协变、逆变、双向协变
关于协变
协变类型保留底层类型之间的子类型关系
如 Triangle 是 Shape 的子类,那么 Triangle[]也是Shape[]的子类,LinkedList<Triangle>是LinkedList<Shape>的子类。
在大部分编程语言中,函数的返回值是协变的,如:
() => Triangle 是 () => Shape 的子类型
关于逆变
逆变是反转了底层类型之间的子类型关系 在大部分编程语言中,函数的参数是逆变的,比如:
(s: Shape) => void 是 (t: Triangle) => void 的子类型,怎么理解?
比如我用到一个类型为 (t: Triangle) => void 变量,我能做的事情是调用这个函数,传入一个 Triangle 类型的变量进行,那么 (s: Shape) => void 类型的变量赋值给 (t: Triangle) => void 会有问题吗,我们看看 Triangle 类型的变量能不能赋值给 (s: Shape) => void,结果是可以的,因为 Triangle 是 Shape 的子类,所以 Shape 有的属性/方法 Triangle 都有
双变
不过 TypeScript 中的函数具有双变性:
https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-function-parameters-bivariant
为什么这样设计呢?看上面链接中的一段代码:
像上面的代码是可以编译通过的,然而在运行时会造成一个错误,就是因为 TS 中函数是双变的,你可能觉得这样不合适,那么想一下,你觉得 Triangle[] 可以赋值给 Shape[] 吗?我们期望是可以的,否则很多地方的代码会变得非常烦人了。比如我们肯定希望 Triangle[] 可以作为 drawShapes(shapes: Shape[]) => void 的参数
那这就要求 Triangle[] 的每个成员都需要能够赋值给 Shape[]
因为数组是有方法的,比如 push 方法,那么 Triangle[].push 需要可以赋值给 Shape[].push
也就是说 (x: Triangle) => number 可以赋值给 (x: Shape) => number
如果上面的描述能够成立的话,这就不仅仅要求函数的参数具有逆变性,还需要有协变性,也就是说,TypeScript 的函数具有双变性,这是设计上的一种 Trade Off
我们可以设置 TS 的--strictFunctionTypes开启函数子类型的强制性检查,这样函数参数就只能逆变了