Typescript的逆变协变于不变

TypeScript 给 JavaScript 添加了一套静态类型系统,是为了保证类型安全的,也就是保证变量只能赋同类型的值,对象只能访问它有的属性、方法。

比如 number 类型的值不能赋值给 boolean 类型的变量,Date 类型的对象就不能调用 exec 方法。

这是类型检查做的事情,遇到类型安全问题会在编译时报错。但是这种类型安全的限制也不能太死板,有的时候需要一些变通,比如子类型是可以赋值给父类型的变量的,可以完全当成父类型来使用,也就是“型变”(类型改变)。这种“型变”分为两种,一种是子类型可以赋值给父类型,叫做协变,一种是父类型可以赋值给子类型,叫做逆变。

协变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name: string;
age: number;
}

interface Chen {
name: string;
age: number;
hobbies: string[];
}
let person: Person;
let chen: Chen = { name: "czt", age: 26, hobbies: ["coding", "game"] };

person = chen;

↑ chen是person的子类型所以可以赋值给person

这种子类型可以赋值给父类型的情况就叫做协变。

##逆变

1
2
3
4
5
6
7
8
9
10
11
12
let printHobbies: (chen: Chen) => void;

printHobbies = (chen) => {
console.log(chen.hobbies);
};

let printName: (person: Person) => void;
printName = (person) => {
console.log(person.name);
};

printHobbies = printName;

函数的参数具有逆变的性质(而返回值是协变的,也就是子类型可以赋值给父类型)
但是在 ts2.x 之前支持这种赋值,也就是父类型可以赋值给子类型,子类型可以赋值给父类型,既逆变又协变,叫做“双向协变”。

但是这明显是有问题的,不能保证类型安全,所以之后 ts 加了一个编译选项 strictFunctionTypes,设置为 true 就只支持函数参数的逆变,设置为 false 则是双向协变。

我们把 strictFunctionTypes 关掉之后,就会发现两种赋值都可以了。

不变

不变最简单,就是不是父子类型的两个类型赋值会报错

1
2
3
4
5
6
7
8
9
10
11
12
interface Robot{
type:string;
hasAI:Boolean
}
interface Chen {
name: string;
age: number;
hobbies: string[];
}
let personA: Person
let robotA: Robot = { type: "T800", hasAI: true };
personA = robotA; //Type 'Robot' is missing the following properties from type 'Person': name, age

总结

总结一句话:结构描述只能收缩且不可逆。