介绍
Typescript里的类型兼容性是基于结构子类型的。结构类型是一种只是用其成员来描述类型的方式,正好与名义类型形成对比。
TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。
关于可靠性的注意事项
Typescript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当作是不可靠的,Typescript允许这种不可靠的发生是经过了仔细考虑的。
开始
typeScript结构化类型的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。只有目标类型的成员会被检查,比较过程递归进行,检查每个成员及子成员。
比较两个函数
let x = (a: number) => 0;let y = (b: number, s: string) => 0;y = x; // OKx = y; // Error
要查看x是否能赋值给y,首先看它们的参数列表。 x的每个参数必须能在y里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。 这里,x的每个参数在y中都能找到对应的参数,所以允许赋值。
第二个赋值错误,因为y有个必需的第二个参数,但是x并没有,所以不允许赋值。忽略额外的参数在JavaScript里是很常见的.
返回值类型
let x = () => ({name: 'Alice'});let y = () => ({name: 'Alice', location: 'Seattle'});x = y; // OKy = x; // Error, because x() lacks a location property
类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。
枚举
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的.
enum Status { Ready, Waiting };enum Color { Red, Blue, Green };let status = Status.Ready;status = Color.Green; // Error
类
类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。
class Animal {feet: number;constructor(name: string, numFeet: number) { }}class Size {feet: number;constructor(numFeet: number) { }}let a: Animal;let s: Size;a = s; // OKs = a; // OK
类的私有成员和受保护成员
类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
泛型
TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。
interface Empty<T> {}let x: Empty<number>;let y: Empty<string>;x = y; // OK, because y matches structure of x
上面代码里,x和y是兼容的,因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下,增加一个成员,就能看出是如何工作的了:
interface NotEmpty<T> {data: T;}let x: NotEmpty<number>;let y: NotEmpty<string>;x = y; // Error, because x and y are not compatible
在这里,泛型类型在使用时就好比不是一个泛型类型。
对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。 然后用结果类型进行比较,就像上面第一个例子。
let identity = function<T>(x: T): T {// ...}let reverse = function<U>(y: U): U {// ...}identity = reverse; // OK, because (x: any) => any matches (y: any) => any
高级主题
子类型与赋值
目前为止,我们使用了“兼容性”,它在语言规范里没有定义。 在TypeScript里,有两种兼容性:子类型和赋值。 它们的不同点在于,赋值扩展了子类型兼容性,增加了一些规则,允许和any来回赋值,以及enum和对应数字值之间的来回赋值。
语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即使在implements和extends语句也不例外
