Typescript类型检查中的协变、逆变和不变

我个人理解,协变、逆变是针对于复杂类(集合)函数之间的类型兼容性关系。主要考虑数组和函数。

先看一些比较权威的定义:
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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值