TypeScript真香系列的内容将参考中文文档,可是文中的例子基本不会和文档中的例子重复,对于一些地方也会深刻研究。另外,文中一些例子的结果都是在代码没有错误后编译为JavaScript获得的。若是想实际看看TypeScript编译为JavaScript的代码,能够访问TypeScript的在线编译地址,动手操做,印象更加深入。javascript
TypeScript中的类型推论,就是当咱们没有明确指定变量的类型时,TypeScript能够自动的推断出变量的数据类型。java
let a = 3; a = 4; a = "s"; //错误,"s"和number类型不匹配
从上面的例子能够看出,当咱们定义了一个变量a,而后进行赋值,TypeScript就自动给咱们推断出变量a的类型。当咱们再给变量a赋值为字符串的时候,就会出现代码中的错误提示。这样的写法在JavaScript中是能够的,可是在TypeScript中给咱们进行了限制。git
let a = { p: "", c: 0 }; a.p = "火影"; a.p = 1; //错误,1和string类型不匹配
上面的例子很简单,可是当咱们定义的变量为数组这样比较复杂的类型的时候,TypeScript就会根据其中的成员来推断出最合适的通用类型:github
let a = [1, 2, null]; a=["s"]; //错误,类型"string"和"number | null"不匹配
上面的例子都是通从右到左判出的类型,TypeScript类型推论也可能按相反的方向来推断,这被叫作“按上下文归类”,按上下文归类会发生在表达式的类型与所处的位置相关时。下面的例子是在函数这一节的:typescript
function sum(a: number, b: number){ return a + b; }
咱们没有指定返回值的类型,可是TypeScript自动从上到下推断出返回值的类型为c#
number。 let man = { a: 1, b: "james", play: (s: string) => { return s } } man.play = function (s){ return s + "s" }
TypeScript中的类型兼容性能够用于肯定一个类型是否能够赋值给其余类型。这里要了解两个概念:数组
结构类型:一种只使用其成员来描述类型的方式;
名义类型:明确的指出或声明其类型,如c#,java。
TypeScript的类型兼容性就是基于结构子类型的。下面的例子:函数
interface IName { name: string; } class Man { name: string; constructor() { this.name = "鸣人"; } } let p: IName; p = new Man(); p.name;
上面的代码在TypeScript不会出错,可是在java等语言中就会报错,由于Man类没有明确的说明实现了IName 接口。可能有人会感受上面的例子体现不了什么,那咱们接下来看下面的不兼容的例子:this
let man: string = "佐助"; let age: number = 20; man = age; // 错误,类型number和类型string不匹配 age = man; // 错误,类型string和类型number不匹配
再看个兼容的例子:rest
let man: any = "佐助"; let age: any = 123 man = age; //123
TypeScript结构化类型系统的基本规则是,若是x要兼容y,那么y至少具备与x相同的属性。以下面的例子:
interface IName { name: string; } let x: IName; let y = {name: "鸣人", age: 123, hero: true}; x = y; //{name: "鸣人", age: 123, hero: true}
这里编译器检查了x中的每个属性,看是否在y中也能找到对应的属性。而上面的 y 符合了 x 兼容的要求,即x兼容y。
interface IName { name: string; age: number } let x: IName; let z = { name: "佐助", cool: true }; x = z; // 错误
这里编译器在检查的时候,发现 z 中少了 x 中的"age"这个属性,因此 x 和 z 是不兼容的。
上面的例子都是一些原始类型或者对象之间的比较,如今咱们看看函数之间是怎么比较的:
let x = (a: number) => 0; let y = (b: number, c: string) => 0; y = x; x = y; //错误
要看x可否赋值给y,先看x和y的参数列表。x的每一个参数都必须在y里面找到对应类型的参数,只要参数类型相对应,参数名字无所谓。上面例子中x的参数都能在y中找到对应的参数,因此容许赋值,可是反过来,y就不能给x赋值。
双向协变包含协变和逆变。协变是指子类型兼容父类型,而逆变正好相反。
let man = (arg: string | number) : void => {}; let player = (arg: string) : void => {}; man = player; player = man;
关于可选参数和rest参数的兼容,能够看下面的例子:
let man = (x: number, y: number) => {}; let work = (x?: number, y?: number) => {}; let play = (...args: number[]) => {}; man = work = play; play = work = man;
关于重载,咱们先看看java中的定义:
在同一个类中,容许存在一个以上的同名函数,只要他们的参数个数或者参数类型不一样便可。与返回值类型无关,只看参数列表(参数的个数、参数的类型、参数的顺序)
在TypeScript中的函数重载和java中的不一样,TypeScript中的函数重载仅仅是参数类型重载:
function sum(a: number, b: number): number; function sum(a: string, b: string): string; function sum(a: any, b: any) { let result = null; if (typeof a === "string" && typeof b === "string") { result = <string>a + "和" + <string>b + "是好基友"; } else if (typeof a === "number" && typeof b === "number") { result = <number>a + <number>b } return result; } sum("鸣人", "佐助"); sum(1, 1);
对于有重载的函数,源函数的每一个重载都要在目标函数上找到对应的函数签名,下面这种方式就是错误的:
function sum(a: number, b: number): number; // 错误,重载签名与其实现签名不兼容 function sum(a: string, b: string): string{ return a + b; };
下面这个例子在TypeScript也不能进行重载:
function sum(a: number, b: number): number{ //错误,函数重复实现 return a + b; }; function sum(a: any, b: any): any{ //错误,函数重复实现 return a + b; };
而后咱们看看返回值类型怎么比较的,源函数的返回类型必须是目标函数返回值的子类型:
interface IMan { x: string; y: number; } interface IPlayer { x: string; y: number; z: number; } let man = (): IMan => ({ x: "鸣人", y: 0 }); let player = (): IPlayer => ({ x: "佐助", y: 0, z: 0 }); man = player; player = man; //错误
从上面能够看出,player是man的子类型,因此man兼容player。下面这个例子也体现了这一点:
interface IMan { x: string; y: number; } interface IPlayer { a: string; b: number; c: number; } let man = (): IMan => ({ x: "鸣人", y: 0 }); let player = (): IPlayer => ({ a: "佐助", b: 0, c: 0 }); man = player; //错误 player = man; //错误
枚举类型和数字类型相互兼容:
enum Man { name, age, } let num = 1; let num2 = 2; let enumNum: Man.name = num; num2 = Man.name;
不一样枚举之间是不兼容的:
enum Man { name, age, } enum Player { name, age, } let man: Man.name = Player.name; //错误,类型Player.name不能分配给类型 Man.name let player: Player.age = Man.age; //错误,类型 Man.name不能分配给类型 Player.name
在TypeScript中,只有实例成员和方法会被比较,静态成员和构造函数不会被比较。
class Man { name: string; constructor(arg: string,) { this.name = arg; } showName() { return this.name; } } class Player { static age: number; name: string; constructor(arg: string, hero: boolean) { this.name = arg; } showName() { return this.name; } } let man = new Man("佐助"); let player = new Player("鸣人", true); man = player; player = man;
从上面的例子能够看出,虽然两个类有着不一样的构造函数和静态成员,可是他们有相同的实例成员和方法,因此他们之间是兼容的。
类的私有成员和受保护成员的兼容性的比较规则是同样的。比较两个类的时候要分两种状况来看,当两个类是父子类,父类中有私有成员的时候,两个类是兼容的;当两个类是同级的类的时候,并且同级类中包含私有或受保护成员时,就不兼容了。看看下面的两个例子:
父子类:
class Man { private name: string; constructor(arg: string) { this.name = arg; } } class Player extends Man { constructor(arg: string) { super(arg); } } let man = new Man("鸣人"); let player = new Player("佐助"); //Man类和Player类是父子类,因此两个类是兼容的 man = player; player = man;
同级类:
class Man { private name: string; constructor(arg: string) { this.name = arg; } } class Player { private name: string; constructor(arg: string) { this.name = arg; } } let man = new Man("鸣人"); let player = new Player("佐助"); man = player; // 错误,类型Player不能分配给类型Man,类型具备私有属性name的单独声明 player = man; // 错误,类型Man不能分配给类型Player,类型具备私有属性name的单独声明
TypeScript泛型的兼容性分两种状况,一种是类型参数没有被成员使用;另外一种是类型参数被成员使用。
咱们先看当类型参数没有被成员使用时:
interface IMan<T>{ } let man1: IMan<number>; let man2: IMan<string>; man1 = man2; man2 = man1;
当类型参数被成员使用时:
interface IMan<T>{ name: T; } let man1: IMan<number>; let man2: IMan<string>; man1 = man2; //错误,IMan<string>不能分配给IMan<number> man2 = man1; //错误,IMan<number>不能分配给IMan<string> interface IMan<T>{ name: T; } let man1: IMan<number>; let man2: IMan<number>; man1 = man2; man2 = man1;
在TypeScript的泛型中,若是类型参数没有被成员使用时,对兼容性没有影响;若是参数被成员使用,则会影响兼容性。
https://github.com/zhongsp/Ty...
https://github.com/jkchao/typ...
文中有些地方可能会加入一些本身的理解,如有不许确或错误的地方,欢迎指出~