ts之协变与逆变

154 阅读2分钟

函数类型

在ts中,对于一个函数的类型,它分为两部分,一部分是入参,一部分则是出参,默认情况下,

对方法:ts对入参采用的是双变模式,而对出参采用的是协变的模式

对函数:ts对入参采用的是逆变模式,而对出参采用的是协变的模式

即:

可以看到上面的代码编译器报错了,下面则没有报编译错误,但是如果你真的传一个Animal进去,那么就只能等着运行时报错吧

为什么ts会对方法有特殊处理

官方是这么解释的,简单来说就是因为历史原因不得不妥协,当然你可以开启严格模式,这样下面这种写法将会报错,配置在这里:

为什么ts要对函数参数采用逆变,而出参采用协变

逆变与协变的概念

协变

用到父类的地方,都可以用子类来替换

逆变

逆变,就是与协变相反,用到子类的地方,只能使用父类来替换

为什么出参要采用协变

其实不止是出参采用协变,除了入参,其他都是采用的协变的模式,这个很好理解,子类可以替代父类嘛,父类有的子类都有

为什么入参要采用逆变

因为对一个函数来说,入参是第三方提供给它的,是它自己在进行消费,这有别于出参,是它自身提供给第三方,由第三方来进行消费,如果采用协变,会导致运行时错误。

举个例子来说:

class Animal {
}

class Dog extends Animal {
  say:()=>string;
}

const func:(val:Animal)=>Dog = (val:Dog)=>{
  val.say();
  return val;
};

func(new Animal());

这里我们给func赋值了一个入参是Dog的函数,如果采用协变,那么调用func(new Animal())必然会产生错误,而如果使用逆变,则在编译时就可以发现这个错误,避免了运行时错误

再举个例子:

[动物,动物].forEach(函数)

你的函数定义

函数(狗){狗.狗叫}

但是调用方,给你的参数是动物,动物不一定都会狗叫,比如人就不会,这时候就会报错了