TypeScript是JS的超集,它可以编译成普通的JavaScript代码。
TypeScript通过类型注解提供编译时的静态类型检查。
官网:http://www.typescriptlang.org/
TypeScript 主要特点包括:
TypeScript 兼容 ECMAScript 2015(ES6)规范
更好的支持OOP。
- 类 Classes
- 接口 Interfaces
- 模块 Modules
- 类型注解 Type annotations
- 编译时类型检查 Compile time type checking
上面的可能用不成,你懂的:
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install
添加watch,改变TS内容后,自动编译。tsc -w xxx.ts
默认编译为ES3,我这里使用:tsc -w -t es6 index.ts
搭建VSCode开发环境
//package.json { "name": "typescript.demo", "version": "1.0.0", "private": true, "main": "index.html", "scripts": { "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", "lite": "lite-server", "postinstall": "typings install", "tsc": "tsc", "tsc:w": "tsc -w", "typings": "typings" }, "license": "ISC", "dependencies": { "bootstrap": "^3.3.6" }, "devDependencies": { "concurrently": "^2.2.0", "lite-server": "^2.2.2", "typescript": "^2.0.2", "typings":"^1.3.2" } }
//tsconfig.json { "compileOnSave": true, "compilerOptions": { "target": "es6", "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, "sourceMap": true } // "include": [ // "src/**/*" // ], // "exclude": [ // "node_modules", // ] }
基础类型
在TS中,声明类型叫法比较诡异,叫类型注解,算了,随它吧。类型注解在TypeScript中是记录函数或变量约束的简便方法
let isDone: boolean = false;
let n: number = 1;
let str: string = "hello";
Array:
let list: number[] = [1,2,3];
let list: Array
元组:
let x: [string,number];
x = ['hello',10];
访问:x[0]
Enum:
enum Color {red,green,blue}; let c: Color = Color.green; //指定起始index enum Color {red=1,Green,Blue}; //根据index取得value let colorName: string = Color[1];
const enum(完全嵌入的枚举)
const enum
声明与正常的enum
在类型安全方面具有同样的作用,只是在编译时会将枚举变量所代表的数字值直接替换到对应位置上。
const enum Color{ Red, Green, Blue, GreenAndBlueSum = Green | Blue } let r = Color.Red; console.log(r); let s = Color.GreenAndBlueSum; console.log(s);
Any:
let v: any = 10; let list: any[] = [1,true,"aaa"];
Void:
function varnUser():void{ alert("aaa"); }
类型断言
//第一种 let someValue: any = "this is a string"; let strLength: number = (someValue).length; //第二种 let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
使用typeof或者instanceof来检查变量类型
if(typeof x ==='string'){}
if(pet instanceof Dog){}
const同let一样是块级作用域,一旦赋值后,不能重新赋值。
const num = 10;
解构数组
let [a,b] = [1,2];
let [a] = [1,2,3];
alert(a);
可以在数组里用...语法创建剩余变量:
let [first, ...rest] = [1, 2, 3, 4]; console.log(first); // outputs 1 console.log(rest); // outputs [ 2, 3, 4 ]
解构对象
let {a,b} = {a:"baz",b:101,c:2}; // ({ a, b } = { a: "baz", b: 101 }); alert(a);
let {a, ...others} = {a:"baz",b:101,c:2};
let n= others.b + others.c;
模块
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。
模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。
模块使用模块加载器去导入其它的模块。 在运行时,模块加载器在执行此模块代码前去查找并执行这个模块的所有依赖。
export
任何声明(比如变量,函数,类或接口)都能够通过添加export
关键字来导出。
//Validation.ts export interface StringValidator { isAcceptable(s: string): boolean; }
//ZipCodeValidator.ts export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } }
也可以这样单独写导出语句
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
export * from "./ZipCodeValidator"; //导出模块中的所有子模块
import
import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator();
//对导入内容重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV();
//将整个模块导入到一个变量,并通过它来访问模块的导出部分 import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
命名空间
任何使用module
关键字来声明一个内部模块的地方都应该使用namespace
关键字来替换。
namespace HelloWorld { export interface Person { SayHello(name:string):string; } export class Student implements Person { SayHello(s: string) { return "hello" + s; } } } HelloWorld.Student s = new HelloWorld.Student(); let str = s.SayHello("jay"); alert(str);
命名空间可嵌套
namespace Shapes { export namespace Polygons { export class Triangle { } export class Square { } } } import polygons = Shapes.Polygons; let sq = new polygons.Square();
命名空间和模块比较
命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象。你可以把所有依赖都放在HTML页面的 标签里。不适合大型应用,很难去识别组件之间的依赖关系
模块可以声明它的依赖。可维护性,代码重用性,封闭性都更好。
不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突。 模块文件本身已经是一个逻辑分组,
//shapes.ts export namespace Shapes { export class Triangle { /* ... */ } export class Square { /* ... */ } }
import * as shapes from "./shapes"; let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
应该改为:
export class Triangle { /* ... */ } export class Square { /* ... */ } import * as shapes from "./shapes"; let t = new shapes.Triangle();
类(可用public或private修饰)
class Person{ //属性 name:string; age:number; //构造函数 constructor(name:string,age:number){ this.name = name; //this表明当前访问的类的成员 this.age = age; } print(){ return this.name + this.age; } } let p = new Person('aa','18'); p.print();
继承 extends
class Animal { name:string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
访问修饰符
public 和 private,protected
类成员默认是public,这里和C#不同
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
class Thing { protected doSomething() { /* ... */ } } class MyThing extends Thing { public myMethod() { // OK,可以在子类里访问受保护的成员 this.doSomething(); } } var t = new MyThing(); t.doSomething(); // Error,不能在类外部访问受保护成员
readonly修饰符
可以使用readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Octopus { readonly name: string; readonly numberOfLegs: number = 8; constructor (theName: string) { this.name = theName; } } let dad = new Octopus("Man with the 8 strong legs"); dad.name = "Man with the 3-piece suit"; // error! name is readonly.
Static
class Person{ static name:string; SayHello(){ alert("Hello"+Person.name); } } Person.name="jay"; let p = new Person(); p.SayHello();
封装属性
class Hello{ private _name:string; get name():string{ return this._name; } set name(n:string){ this._name = n; } } let h = new Hello(); h.name = "aaa";
抽象类
abstract class Animal{ abstract makeSound():void; move():void{ console.log('moving'); } } class Duck extends Animal{ makeSound():void{ console.log('ga.ga.ga'); } move():void{ console.log('duck move'); } }
Duck duck = new Duck();
duck.makeSound();
函数
//先介绍两种JS的function写法 //命名函数 function add(x,y){ return x + y; } //匿名函数 var myAdd = function(x,y){ return x+y; } //TS命名函数 function add(x:number,y:number):number{ //TypeScript能够根据返回语句自动推断出返回值类型,返回类型可省略
return x+y;
}
//TS匿名函数
let myAdd = function(x:number,y:string):number{ return x+y; }
//TS箭头函数 =>后的返回值类型不能省略
let myAdd: (x:number, y:number)=>number = function(x: number, y: number): number { return x+y; };
let myAdd: (x:number, y:number)=>number =x+y;
let myAdd: (baseValue:number, increment:number) => number = function(x, y) { return x + y; }; //推断类型
支持可选参数,可选参数必须跟在必须参数后面。
function buildName(firstName: string, lastName?: string) { if (lastName) return firstName + " " + lastName; else return firstName; }
支持默认参数
剩余参数
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
函数重载
typescript中的重载需要动态实现,和C#是有区别的。这里的重载主要是为了静态化,通过静态化能使用户提前发现错误,给用户提供智能感知,这里的静态化都是形式上的。
function attr(name:string):string; function attr(age:number):number; function attr(a:any):any{ if (n && typeof n ==="string"){ alert("name"); } else{ alert("age"); } } attr(10);
interface
先看个例子
//要求参数必须包含一个string类型的label属性 function printLabel(labelledObj: { label: string }) { console.log(labelledObj.label); } let myObj = { size: 10, label: "Size 10 Object" }; printLabel(myObj);
使用interface改写:
interface LabelledValue { label: string; } function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label); } let myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
接口的可选属性
interface IPerson{ name:string; age?:number; } function PrintStudent(obj:IPerson){ console.log(obj.name); } let student = {name:'jay'}; PrintStudent(student);
接口的只读属性
interface Point { readonly x: number; readonly y: number; } let p1: Point = { x: 10, y: 20 }; p1.x = 5; // error!
readonly
vs const
变量:const
属性:readonly
接口中的函数类型
//函数类型的接口 interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); if (result == -1) { return false; } else { return true; } }
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。只要参数类型是兼容的就行。 如果你不想指定类型,Typescript的类型系统会推断出参数类型,
接口中的可索引类型
可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string; //这里可以用 readonly修饰
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
alert(myStr);
接口中的类类型
同C#里的接口作用基本一样,定义契约,要求别的实现类去实现它
interface IClock{ currentTime:Date; setTime(d:Date); } class Clock implements IClock{ currentTime:Date; setTime(d:Date){ this.currentTime = d; } constructor(h:number,m:number){ } }
接口继承(扩展接口)
interface Person{ name:string; age:number; } interface ChinesePeple extends Person{ color:string; } let p ={}; p.color="yellow"; p.name="jay";
泛型
//泛型类
class Person{ name:T sayHello:(name:T) =>T; } let p = new Person (); p.name="jay"; p.sayHello = function(name){ return "Hello" + name; } alert(p.sayHello("jay"));
//泛型方法 function Hello(arg:T):T{ return arg; } let str = Hello ("hello jay"); //let str = Hello("Hello jay");这里编译器会做类型推断,不必用<>明确传入类型。
泛型约束
function Hello(arg:T):T{ console.log(arg.length); // Error: T doesn't have .length return arg; } let h = Hello (10); //undefined
interface Lengthwise { length: number; } function Hello(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; } Hello(3); // Error, number doesn't have a .length property Hello({length: 10, value: 3}); //succeed Hello("Hello jay");//succeed
在泛型约束中使用类型参数
function copyFields(target: T, source: U): T { for (let id in source) { target[id] = source[id]; } return target; } let x = { a: 1, b: 2, c: 3, d: 4 }; copyFields(x, { b: 10, d: 20 }); // okay copyFields(x, { Q: 90 }); // error: property 'Q' isn't declared in 'x'.
interface Named { name: string; } class Person { name: string; } let p: Named; // OK, because of structural typing p = new Person();
类型系统分为:
名义性类型系统:数据类型的兼容性是通过明确的声明或类型名称来决定
结构性类型系统:基于类型的组成结构,不要求明确地声明
interface Named { name: string; } let x: Named; // y's inferred type is { name: string; location: string; } let y = { name: 'Alice', location: 'Seattle' }; x = y; //这里要检查y
是否能赋值给x
,编译器检查x
中的每个属性,看是否能在y
中也找到对应属性。//y
有个额外的location
属性,但这不会引发错误。 只有目标类型(这里是Named
)的成员会被一一检查是否兼容。
function greet(n: Named) { alert('Hello, ' + n.name); } greet(y); // OK
函数兼容性
let x = (a: number) => 0; let y = (b: number, s: string) => 0; y = x; // OK x的每个参数必须在y里有对应的参数,参数名称是否相同没关系,只看参数类型 x = y; // Error
let x = () => ({name: 'Alice'}); let y = () => ({name: 'Alice', location: 'Seattle'}); x = y; // OK y = x; // Error because x() lacks a location property
交叉类型
交叉类型是将多个类型合并为一个类型,这个类型的对象将同时拥有多个类型的成员。
function extend(first: T, second: U): T & U { let result = {}; for (let id in first) { ( result)[id] = ( first)[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { ( result)[id] = ( second)[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
联合类型
当一个函数参数需要指定多个类型时,我们可以
function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4); // returns " Hello world"
let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错
更好的方式是用联合类型:
function padLeft(value: string, padding: string | number) { // ... } let indentedString = padLeft("Hello world", true); // errors during compilation
Symbols(ES6)
ES6新加入的一个原生类型,不是 Undefined,Null,Boolean,Number,String,Object任何一种,是JS中的第7种数据类型。
let sym1 = Symbol(); let sym2 = Symbol("key"); // 可选的字符串key
let sym2 = Symbol("key"); let sym3 = Symbol("key"); sym2 === sym3; // false, symbols是唯一的
symbols也可以被用做对象属性的键。
let sym = Symbol(); let obj = { [sym]: "value" }; console.log(obj[sym]); // "value"
symbols用于类成员函数
const getClassNameSymbol = Symbol(); class C { [getClassNameSymbol](){ return "C"; } } let c = new C(); let className = c[getClassNameSymbol](); // "C"
迭代器和生成器
for..of
vs. for..in
语句
for..in
迭代的是对象的 键 的列表,而for..of
则迭代对象的键对应的值。
let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2", } for (let i of list) { console.log(i); // "4", "5", "6" }
声明合并
接口合并(成员属性不能同名,函数同名会当作重载合并)
interface Box { height: number; width: number; } interface Box { scale: number; } let box: Box = {height: 5, width: 6, scale: 10}; console.log(box.height);
命名空间合并
与接口相似,合并其成员,非导出成员仅在其原有的(合并前的)命名空间内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。
namespace Animal { let haveMuscles = true; export function animalsHaveMuscles() { return haveMuscles; } } namespace Animal { export function doAnimalsHaveMuscles() { return haveMuscles; // <-- error, haveMuscles is not visible here } }
Mixins混入
针对类与类之间混入,传统方式是使用继承,我们也可以通过可重用组件创建类的方式,
// Disposable Mixin class Disposable { isDisposed: boolean; dispose() { this.isDisposed = true; } } // Activatable Mixin class Activatable { isActive: boolean; activate() { this.isActive = true; } deactivate() { this.isActive = false; } } class SmartObject implements Disposable, Activatable { //没使用extends
而是使用implements
。 把类当成了接口,仅使用Disposable和Activatable的类型而非其实现。 constructor() { setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500); } interact() { this.activate(); } // Disposable isDisposed: boolean = false; //为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 dispose: () => void; // Activatable isActive: boolean = false; activate: () => void; deactivate: () => void; } applyMixins(SmartObject, [Disposable, Activatable]); let smartObj = new SmartObject(); setTimeout(() => smartObj.interact(), 1000); //////////////////////////////////////// // In your runtime library somewhere //////////////////////////////////////// function applyMixins(derivedCtor: any, baseCtors: any[]) { //混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码。 baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { derivedCtor.prototype[name] = baseCtor.prototype[name]; }); }); }