Typescript - 怎么理解协变和逆变


Typescript - 怎么理解协变和逆变

文章插图
 
首先协变(Covariance)和逆变(contravariance),这俩概念不是TS特有的,很多有类型系统的语言都有一样的概念,比如C#,JAVA等 。要理解这两个概念,让我们先建立几个类,然后再详细说明,如下:
Typescript - 怎么理解协变和逆变

文章插图
 
代码很直观,建立了3个类,动物,狗,黄狗,它们之间的继承关系是,动物(Animal)类是基类,狗(Dog)继承自动物类,黄狗(YellowDog)继承自狗类 。Animal <- Dog <- YellowDog 它们每个类都有各自特有的属性 。
接下来,我们来为每个类创建一个实例对象:
Typescript - 怎么理解协变和逆变

文章插图
 
协变(Covariance)
根据微软的解释:协变是使您能够使用比最初指定更多的派生类型 。这是什么意思?其实就是指,派生类型的值可以安全的赋给基类型(继承自的类型),而反过来就不行 。
比如本例中,黄狗的实例,就可以赋值给类型为狗或动物的变量,狗的实例可以赋值给动物类型的变量,但反过来,狗的实例,就不可以赋值给黄狗类型的变量,如下:
Typescript - 怎么理解协变和逆变

文章插图
 
我觉得这也可以理解,因为这样的赋值是安全的 。属性多的实例,赋值给属性少的类型,不会丢失数据 。黄狗类包含了动物类所有的成员,所以当黄狗对象赋值给动物类型时,动物类型的每个字段属性都可以被正常赋值,如果反过来,用动物实例对象给黄狗类型赋值,那动物对象中就不存在黄狗对象所需要的字段,视为不安全赋值,所以编译报错 。
逆变(contravariance)
也是根据微软的解释:跟协变正好相反,逆变使您能够使用比最初指定的更通用(较少派生)的类型 。
这通常发生在函数类型的参数中,看下面的代码:
Typescript - 怎么理解协变和逆变

文章插图
 
我们定义了三个函数类型,然后为每个函数类型定义了一个函数的实例 。
Typescript - 怎么理解协变和逆变

文章插图
【Typescript - 怎么理解协变和逆变】 
接下来,我们定义了一个函数,它的参数是一个FuncDog的函数类型 。让我们看看把每个函数传进来会有什么结果,
Typescript - 怎么理解协变和逆变

文章插图
 
现在反而是 funYellowDog 参数报错了,因为它是继承自Dog,跟协变相反,所以它会报错 。那要怎么理解这个呢?为啥会这样,我是这么理解的,
让我们改一下这个函数,让它做点事:
Typescript - 怎么理解协变和逆变

文章插图
 
因为传入的函数参数终究还是要被调用的,按理它是需要一个狗的对象,所以我们实例化一个dog对象,然后调用 func(dog) ,这时如果使用 FuncAnimal 类型的函数,没有问题,因为它需要的参数是 Animal 类型,所以dog可以赋值给它 。正好又符合了协变 。
但如果这里允许传入 FuncYellowDog 类型的函数,那当它调用的时候,dog就想当于要赋值给 YellowDog 类型的变量,这就又是不安全的赋值 。跟协变里面的错误是一样的 。所以就禁止这样做了 。


推荐阅读