TypeScript 进阶 类型兼容性
TypeScript 里的类型兼容性是 基于结构子类型 的。 结构类型 是一种只使用其成员来描述类型的方式。它正好与名义(nominal)类型形成对比。在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();
在使用基于名义类型的语言,比如 C#或 Java 中,这段代码会报错,因为 Person 类没有明确说明其实现了 Named 接口。
TypeScript 结构化类型系统的基本规则
如果 x
要兼容 y
,那么 y
至少具有与 x
相同的属性
interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
// 这里要检查 y 是否能赋值给 x,编译器检查 x 中的每个属性,看是否能在 y 中也找到对应属性。
// y 必须包含名字是 name 的 string 类型成员。y 满足条件,因此赋值正确。
x = y;
检查 函数参数时 使用相同的规则:
function greet(n: Named) {
console.log('Hello, ' + n.name);
}
greet(y); // 有个额外的 location 属性,但这不会引发错误。 只有目标类型(这里是 Named)的成员会被一一检查是否兼容。这个比较过程是递归进行的,检查每个成员及子成员。
比较两个函数
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // x 要赋值给 y,x 的每个参数必须能在 y 里找到对应类型的参数。注意的是参数的名字相同与否无所谓,只看它们的类型。
x = y; // Error 赋值错误,因为 y 有个必需的第二个参数,但是 x 并没有,所以不允许赋值。
比较返回值类型
类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error, because x() lacks a location property
函数参数双向协变
enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
// 不健全的,但是有用的,普通的
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
// 在健全的情况下不需要的替代品
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
// 仍然不允许(明显错误)。对完全不兼容的类型强制执行类型安全
listenEvent(EventType.Mouse, (e: number) => console.log(e));
可选参数及剩余参数
当一个函数有剩余参数时,它被当做无限个可选参数。
这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些 undefinded
。
有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用:
function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}
// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
枚举
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。
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; // OK
s = a; // OK
类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类 。
泛型
类型参数只影响使用其做为类型一部分的结果类型
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // y 匹配 x 的结构
上面代码里, x
和 y
是兼容的,因为 它们的结构使用类型参数时并没有什么不同 。
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error, 因为 x 和 y 是不兼容的。在这里,泛型类型在使用时就好比不是一个泛型类型。
对于没指定泛型类型的泛型参数时,会把所有泛型参数当成 any
比较,然后用结果类型进行比较 。
let identity = function<T>(x: T): T {
// ...
}
let reverse = function<U>(y: U): U {
// ...
}
identity = reverse; // OK, 因为 (x: any) => any 匹配 (y: any) => any
小结
- 赋值语句左边的 数据类型 必须是 右边的 数据类型 的子类型。除了函数参数 右边的 是 左边的 子类型
- 当一个函数有剩余参数时,它被当做无限个可选参数。
- 对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。
- 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。
- 类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内
- 类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类
- 对于没指定泛型类型的泛型参数时,会把所有泛型参数当成
any
比较,然后用结果类型进行比较 。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论