Typescript中的逆变与协变
2022-11-11
| 2023-2-19
0  |  0 分钟
password
Created
Jan 31, 2023 07:50 AM
type
Post
status
Published
date
Nov 11, 2022
slug
summary
逆变(contravariant): 逆转了子类型序关系 协变(covariant): 保持了子类型序关系 双向协变(bivariant): 可以相互分配 不变(invariant): 无法相互分配
tags
Typescript
category
原理
icon
fas fa-info-circle
逆变(contravariant): 逆转了子类型序关系
协变(covariant): 保持了子类型序关系
双向协变(bivariant): 可以相互分配
不变(invariant): 无法相互分配
 
  1. 在ts的类型系统中,如果T可分配给U,ts只关心这个类型的表现是什么,不关心这个类型叫什么名称,只要T的表现跟U一样,ts认为T可分配给U是安全的。
class Animal { public weight: number = 0; } class Dog extends Animal { public wang() { console.log("wang") } } class Cat extends Animal { public miao() { console.log("miao") } } declare const cat:Cat const animal:Animal = cat // work
 
  1. 函数属性的入参是逆变, 但函数方法的入参是双向协变
造成这个问题的主要原因是: 函数属性会被编译成constructor中的局部函数声明, 但是函数方法会被编译成原型链上的对象, 也就是其调用的本质是不一样的
interface Animal { name: string; } interface Dog extends Animal { wang: () => void; } interface Cat extends Animal { miao: () => void; } interface Comparer<T> { // 使用函数属性声明的compare方法, 遵循逆变规则 compare: (a: T, b: T) => number; // 使用函数方法声明的compare方法, 遵循双向协变规则 compare(a: T, b: T): number; } declare let animalComparer: Comparer<Animal>; declare let dogComparer: Comparer<Dog>; // 使用函数属性声明的compare方法, 报错 // 使用函数属性声明的compare方法, 正常 animalComparer = dogComparer; dogComparer = animalComparer; // Ok
本质原因如下面代码示例
class Greeter { constructor() { this.greet(); this.greet2(); this.greet3(); } greet() { console.log('greet1', this); } greet2 = () => { console.log('greet2', this); } greet3 = function() { console.log('greet3', this); } } let bla = new Greeter(); const b: Array<Greeter> = []; // 会被编译为如下 // 可以看到, 函数属性声明的greet会被定义到原型链上 // 但是, 函数方法如greet2 greet3会被定义到 var Greeter = /** @class */ (function () { function Greeter() { var _this = this; this.greet2 = function () { console.log('greet2', _this); }; this.greet3 = function () { console.log('greet3', this); }; this.greet(); this.greet2(); this.greet3(); } Greeter.prototype.greet = function () { console.log('greet1', this); }; return Greeter; }()); var bla = new Greeter();
 
  1. Array中的方法
Array中的方法使用的是函数的方法, 也就是非箭头函数的形式
无疑这样会导致方法的执行可以双向协变, 导致类型不安全
 
总结来说:
  1. 类的扩展类型, 可以赋值给原类, 这个称为协变
  1. 类的扩展类型中调用了原类不支持的属性或方法, 则无法转类型至原类, 这个称为逆变
  1. 类中函数的声明方式决定了其遵循逆变还是双向协变, 如果使用箭头函数声明方法, 遵循逆变, 否则遵循双向协变, 此时支持直接的父子类调用关系, 类型相对不安全.
  1. 数组中的方法, 因为不同的规则, 实现的方式是让其遵循双向协变
原理
  • Typescript
  • Go语言实战读书笔记Go语言圣经读书笔记
    目录