Skip to content

类型系统

编程的本质是操作数据,而类型系统旨在于限制对数据的操作,类型的本质就是数据的操作空间

操作空间

操作空间指一个数据被允许的所有操作,比如:数字只能和数字相乘,而不能和字符串相乘。把类型看作操作空间好像又复杂又多余,但在后面的章节中你将通过这种理解收获良多。

ts
interface Cat {
    name: string;
    walk(): void;
}

如上,Cat 类型的含义是:当一个数据的类型为 Cat 时, 它能执行的操作有:被读取 name 属性并返回 string 类型的数据、执行 walk 方法并返回 void 类型的数据,这些操作组成了这个数据的操作空间。

类型继承

ts
interface Animal {
    eat(): void;
}
interface Cat extends Animal {
    name: string;
    walk(): void;
}

通过对 Animal 的继承,现在类型 Cat 的数据可以执行 eat 方法了,这说明 Cat 的操作空间扩大了。 可见类型继承的本质就是操作空间的融合。

类型兼容

类型兼容就是把一种类型的数据当作另一种类型的数据来操作。 当类型 A 继承于类型 B 时,类型 A 其实就包含了类型 B 允许的所有操作,也就是说可以把类型 A 的数据当作类型 B 的数据来操作,所以我们可以放心地把类型 A 的数据赋给类型 B 的变量。

变量类型

这里还需要明确的一点是:既然类型对数据来说表示这个数据的操作空间,那么对变量来说意味着什么? 我们可以把变量看作数据的容器,变量的类型表示这个容器内的数据应该能够执行的操作,如果外部数据的操作空间不包含这些操作,则数据不能进入该容器。 也就是说,变量的类型是表示该变量所承载数据的最小操作空间,它是决定数据能否被赋给变量最低标准。

综上,类型兼容的条件可以概括为:SpaceSpace。 基于此,我们可以得出判断类型兼容的基本步骤:

  1. 分别确定发送端和接收端的操作空间
  2. 对比操作空间大小

接下来看一些简单的例子:

ts
let a = { name: 'a' };
let b: { name: string; age: number } = a;

变量 a 的数据的操作空间是读取 name 属性并返回 string 类型,变量 b 要求的最小操作空间还有读取 age 属性并返回 number 类型。 显然变量 a 不满足变量 b 的最小操作空间,所以赋值报错。

ts
let a = { name: 0 };
let b: { name: string } = a;

变量 a 的数据的操作空间是读取 name 属性并返回 number 类型,变量 b 要求的最小操作空间是读取 name 属性并返回 string 类型。 虽然它们都能被读取 name 属性,但是 number 类型的数据不能当作 string 来操作,所以赋值报错。 但是如果把变量 a 数据的 name 改成 string 的子类型,就能赋值成功,因为子类型总是兼容父类型。

函数

函数的特殊之处在于它既能输入数据(传参)也能输出数据(返回值),对于不同的数据流向需要分别讨论。

传参

ts
let a = (e: { name: string }) => {};
let b: (e: { name: string; age: number }) => void = a;

在分析前,我们首先得关注一个问题:类型对参数来说意味着什么?参数的本质就是函数内变量,所以参数类型和变量类型的含义是一样的,表示接收端所要求的最小操作空间。

明白了这个后,再看当 a 赋给 b 后,是谁在给 ab 传参:

根据类型兼容原理,从外部传给 b 的数据需要通过 b 的参数类型检查,检查通过后直接发送给 a。 所以需要事先确保:经过 b 参数类型检查的数据也能通过 a 参数类型检查,也就是说 b 的参数类型表示的操作空间应该比 a 的大。

综上,函数参数类型兼容的条件是:SpaceSpace,这种现象被称为逆变。 这种现象其实很好理解,平常我们传一个数据给另一个变量,有很明显的发送和接收端,就像在示例中:a 是发送端,b 是接收端。 但如果传输的数据是函数类型,原本作为接收端的变量承担了发送参数的义务,所以在参数传递上,原本的接收端此时成为了发送端。

返回值

ts
let a = () => ({ name: string });
let b: () => { name: string; age: number } = a;

既然参数传递是从 b 到 a,那么返回值传递很自然地就是从 a 到 b。 既然想把 a 的返回值传递给 b 的返回值,就需要通过类型检查,此时只需要确保 a 的返回值的操作空间比 b 的大就行了。

即函数返回值类型兼容的条件是:SpaceSpace,这种现象被称为协变。

https://www.bilibili.com/video/BV1EF4m1u7AE