类型界限的进阶认知:TypeScript 之 协变与逆变
2023-09-03 01:05:24
在 TypeScript 中,父子类型在编程理论上是一个复杂的话题,而它的复杂之处来自于一对经常会被混淆的现象,我们称之为协变与逆变。协变和逆变是两个密切相关的概念,它们了子类型与父类型之间如何相互转换。
协变
协变是指在类型系统中,子类型的对象可以赋值给父类型。例如,如果 Animal 是一个父类,而 Cat 是一个子类,那么一只 Cat 对象可以赋值给一个 Animal 类型的变量。这是因为 Cat 对象拥有所有 Animal 对象拥有的属性和方法,因此它可以满足 Animal 类型的所有要求。
逆变
逆变是指在类型系统中,父类型的对象可以赋值给子类型。例如,如果 Animal 是一个父类,而 Cat 是一个子类,那么一个 Animal 类型的变量可以接受一个 Cat 对象。这是因为 Animal 类型允许的任何值也都是 Cat 类型允许的。
在 TypeScript 中使用协变和逆变
在 TypeScript 中,协变和逆变可以通过使用泛型来实现。泛型允许您创建可以接受不同类型参数的类型。通过使用泛型,您可以定义一个父类型,它可以接受不同类型的子类型。例如,您可以定义一个名为 Animal 的父类,它具有一个名为 name 的属性。您还可以定义一个名为 Cat 的子类,它继承了 Animal 类并具有一个名为 meow() 的方法。
您可以使用泛型来定义一个函数,它可以接受不同类型的 Animal 对象。例如,您可以定义一个名为 printAnimalName() 的函数,它接受一个 Animal 类型参数,并打印该参数的 name 属性。
function printAnimalName<T extends Animal>(animal: T): void {
console.log(animal.name);
}
您可以使用这个函数来打印任何类型的 Animal 对象,包括 Cat 对象。
const cat = new Cat("Kitty");
printAnimalName(cat); // 输出 "Kitty"
协变和逆变的局限性
在 TypeScript 中,协变和逆变的使用存在一些局限性。协变只能用于方法的参数类型,而逆变只能用于方法的返回类型。这意味着您不能将子类型的对象赋值给父类型的变量,也不能将父类型的对象赋值给子类型的参数。
例如,以下代码是无效的:
class Animal {
name: string;
}
class Cat extends Animal {
meow(): void {
console.log("Meow!");
}
}
function printAnimalName(animal: Animal): void {
console.log(animal.name);
}
const cat = new Cat("Kitty");
printAnimalName(cat); // 错误:类型“Cat”不能赋值给类型“Animal”
这是因为 Cat 类型不是 Animal 类型的子类型。Cat 类型具有 Animal 类型没有的属性和方法,因此它不能赋值给 Animal 类型的变量。
总结
协变和逆变是 TypeScript 中两个重要的概念。它们允许您创建更加灵活和强大的类型系统。协变和逆变可以通过使用泛型来实现。协变只能用于方法的参数类型,而逆变只能用于方法的返回类型。协变和逆变的使用存在一些局限性,但它们仍然是 TypeScript 中非常有用的工具。