我个人理解,协变、逆变是针对于复杂类(集合)函数之间的类型兼容性关系。主要考虑数组和函数。
先看一些比较权威的定义:
https://baike.baidu.com/item/%E5%8D%8F%E5%8F%98/10963814?fr=aladdin
协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
逆变(contravariant),如果它逆转了子类型序关系。
不变(invariant),如果上述两种均不适用。
首先考虑数组类型构造器: 从Animal类型,可以得到Animal[](“animal数组”)。 是否可以把它当作
协变:一个Cat[]也是一个Animal[]
逆变:一个Animal[]也是一个Cat[]
以上二者均不是(不变)?
最简单的理解:
1,协变:复杂类型保持了具体类型的包含关系:一群猫是一群动物
2,逆变:复杂类型关系和具体类类型关系相反:一群动物是一群猫。
在数组等容器中,协变是支持的,但并不安全,这个应该比较容易理解。
// 先定义类之间的关系
class Animal {
name:string = "myName";
constructor(n: string) {
console.log(`Animal.constructor :: enter, n = ${n}`)
this.name = n;
}
move(x:number, y:number):void {
console.log(`Animal.move :: x = ${x}, y = ${y}`)
}
}
class Cat extends Animal {
meow() {
console.log(`Cat.meow :: enter.`)
}
}
class Dog extends Animal {
wow() {
console.log(`Dog.wow :: enter, name = ${this.name}`)
}
}
/**
* 测试协变,协变的时候,读list的写是安全的,但是,可能会导致读不安全
*/
function test1 () {
let catArr:Cat[] = [new Cat("C1"), new Cat("C2")];
let animalArr:Animal[];
// 协变,
animalArr = catArr;
animalArr.push(new Dog("D3")); // 写安全
// // 此时,对catArr的读不安全
catArr.forEach(cat=>cat.meow())
}
test1();
在函数类型比较时,函数参数的类型比较,使用的是逆变。
/**
* 测试逆变
*/
function test2() {
let showAin = (ani:Animal)=>0; // 这里省略了函数类型限定
let showCat = (cat:Cat) => 0; // 这里省略了函数类型限定
// 函数的类型检查是逆变的
showCat = showAin; // showAin参数范围大,showCat参数范围小
// 反过来可不行
// showAin = showCat;
}
test2()
上面不太好理解,理解上去确实费劲。
我们得从函数的本质上去看
函数就是接收参数,然后做一些处理,最后返回结果。函数就是一系列操作的集合,而对于一个具体的类型Cat
作为参数,函数不仅仅可以把它当成Animal
,来执行一些操作;还可以访问其作为Cat
独有的一些属性和方法,来执行另一部分操作。因此Action<Cat>
的操作肯定比Action<Animal>
要多,因此后者是前者的子集,兼容性是相反的,是逆变。
function testNB2() {
// 增加函数的类型限定
let showAin:(ani:Animal) =>void = (ani:Animal)=>{
ani.move(5,10);
};
let showAin2:(cat:Cat) =>void = (ani:Animal)=>{
ani.move(5,10); // 从Animal上继承的方法
// 这里只能使用范围更大,公共部分的方法。
};
// 直接报错
let showAin3:(ani:Animal) =>void = (cat:Cat)=>{
cat.move(5,10); // 从Animal上继承的方法
cat.meow(); // 自己特有的方法
};
let showCat = (cat:Cat) => 0;
}
/**
* 用函数的参数限定,来直观的测试逆变
*/
function testNB() {
const readContentAsnyc = (path:string, callback:(err:Error, result:string)=>void):void =>{
callback(null, "hello"); // callback调用的时候,一定会传入要求的参数
}
// 回调函数参数范围较小,只关注err,不会报错,因为只会使用参数err,而回调的时候,一定会传
readContentAsnyc("file.js", (err:Error)=>{
console.log(err)
})
// 回调函数的参数范围比要求的多,反而不行,callback 调用的时候只保证传入2个参数,
// 而 callback 定义的时候用了更多参数duration,duration根本不会被传入,无法正常保证使用
readContentAsnyc("file.js", (err:Error, result:string, duration:number)=>{
console.log(duration)
})
}
参考:
https://segmentfault.com/a/1190000022724899
https://baike.baidu.com/item/%E5%8D%8F%E5%8F%98/10963814?fr=aladdin