类型兼容

TypeScript里的类型兼容性是基于结构类型系统的,只要二者的结构组成相同,就认为二者属于统一类型

1
2
3
4
5
6
7
8
9
interface Pet {
name: string;
}
class Dog {
name: string;
}
let pet: Pet;
// OK, because of structural typing
pet = new Dog();

关于可靠性的注意事项

TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的

TypeScript结构类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的成员。可以有额外的属性存在。

比较两个函数

首先看它们的参数列表,要求类型相同

函数参数双向协变/函数协逆变

协变

当子类型包含父类型的属性时,允许子类型的变量赋值给父类型的变量,称为协变

逆变

参数为父类型的函数可以被赋值给参数为子类型的函数,称为逆变。
假如颠倒过来,就变成了类型不安全的情况。因为父类型的约束更少,范围更广,子类型的约束更多。子类型中的属性可能父类型中并没有要求

在ts2.x前支持父类型可以赋给子类型,子类型同样可以赋给父类型,称为双向协变。但因为双向协变的不安全性,ts之后加入了 strictFunctionTypes,设置为true即可关闭双向协变的支持

默认情况下,一个函数类型中返回值类型是协变的,而参数类型是逆变的

TypeScript 中的子类型、逆变、协变是什么

可选参数及剩余参数

比较函数兼容性的时候,可选参数与必须参数是可互换的,即二者在类型判断上没有区别,剩余参数则是被当做无限个可选参数

从运行时角度来看,可选参数一般不强制,因为对于大部分函数来说相当于传递了一些undefinded

函数重载

对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名

枚举

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的

1
2
3
4
5
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
status = Color.Green; // Error

与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型

比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内

1
2
3
4
5
6
7
8
9
10
11
12
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

类的私有成员和受保护成员

当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员

这条规则也适用于包含受保护成员实例的类型检查。允许子类赋值给父类,但是不能赋值给其它有同样类型的类

泛型

类型参数只影响使用其做为类型一部分的结果类型

1
2
3
4
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string>;
x = y; // OK, 因为y和x的结构相匹配

当结果类型使用了类型参数时,比较会发生变化

1
2
3
4
5
6
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error, 因为x和y不兼容
作者

徐云飞

发布于

2022-10-31

更新于

2023-02-05

许可协议

评论