自我介绍:大家好,我是吉帅振的网络日志(其他平台账号名字相同),互联网前端开发工程师,工作5年,去过上海和北京,经历创业公司,加入过阿里本地生活团队,现在郑州北游教育从事编程培训。
TypeScript 中类型的兼容性都是基于结构化子类型的一般原则进行判定的。
(1)子类型
从子类型的角度来看,所有的子类型与它的父类型都兼容,如下代码所示:
{
const one = 1;
let num: number = one; // ok
interface IPar {
name: string;
}
interface IChild extends IPar {
id: number;
}
let Par: IPar;
let Child: IChild;
Par = Child; // ok
class CPar {
cname = '';
}
class CChild extends CPar {
cid = 1;
}
let ParInst: CPar;
let ChildInst: CChild;
ParInst = ChildInst; // ok
let mixedNum: 1 | 2 | 3 = one; // ok
}
在示例中的第 3 行,我们可以把类型是数字字面量类型的 one 赋值给数字类型的 num。在第 12 行,我们可以把子接口类型的变量赋值给 Par。在第 21 行,我们可以把子类实例 ChildInst 赋值给 ParInst。因为成员类型兼容它所属的类型集合(其实联合类型和枚举都算类型集合,这里主要说的是联合类型),所以在示例中的第 22 行,我们可以把 one 赋值给包含字面类型 1 的联合类型。由子类型组成的联合类型也可以兼容它们父类型组成的联合类型,如下代码所示:
let ICPar: IPar | CPar;
let ICChild: IChild | CChild;
ICPar = ICChild; // ok
在示例中的第 3 行,因为 IChild 是 IPar 的子类,CChild 是 CPar 的子类,所以 IChild | CChild 也是 IPar | CPar 的子类,进而 ICChild 可以赋值给 ICPar。
(2)结构类型
类型兼容性的另一准则是结构类型,即如果两个类型的结构一致,则它们是互相兼容的。比如拥有相同类型的属性、方法的接口类型或类,则可以互相赋值。
{
class C1 {
name = '1';
}
class C2 {
name = '2';
}
interface I1 {
name: string;
}
interface I2 {
name: string;
}
let InstC1: C1;
let InstC2: C2;
let O1: I1;
let O2: I2;
InstC1 = InstC2; // ok
O1 = O2; // ok
InstC1 = O1; // ok
O2 = InstC2; // ok
}
因为类 C1、类 C2、接口类型 I1、接口类型 I2 的结构完全一致,所以在第 18-19 行我们可以把类 C2 的实例 InstC2 赋值给类 C1 的实例 Inst1,把接口类型 I2 的变量 O2 赋值给接口类型 I1 的变量 O1。在第 20~21 行,我们甚至可以把接口类型 I1 的变量 O1 赋值给类 C1 的实例,类 C2 的实例赋值给接口类型 I2 的变量 O2。另外一个特殊的场景:两个接口类型或者类,如果其中一个类型不仅拥有另外一个类型全部的属性和方法,还包含其他的属性和方法(如同继承自另外一个类型的子类一样),那么前者是可以兼容后者的。
{
interface I1 {
name: string;
}
interface I2 {
id: number;
name: string;
}
class C2 {
id = 1;
name = '1';
}
let O1: I1;
let O2: I2;
let InstC2: C2;
O1 = O2;
O1 = InstC2;
}
在示例中的第 16~17 行,我们可以把类 C2 的实例 InstC2 和接口类型 I2 的变量 O2 赋值给接口类型 I1 的变量 O1,这是因为类 C2、接口类型 I2 和接口类型 I1 的 name 属性都是 string。不过,因为变量 O2、类 C2 都包含了额外的属性 id,所以我们不能把变量 O1 赋值给实例 InstC2、变量 O2。这里涉及一个需要特别注意的特性:虽然包含多余属性 id 的变量 O2 可以赋值给变量 O1,但是如果我们直接将一个与变量 O2 完全一样结构的对象字面量赋值给变量 O1,则会提示一个 ts(232