前文
最近在学习ts
好多乱七八糟的概念都不懂
一之哥耐心辅导了两天才明白了一个大概概念
记录一下
协变
我们可以假设一个
class animal{
xxxx
}
class dog extends animal{
xxxx
}
这个时候animal是dog的父类
如果我们将子类的实例赋值给数据类型为父类的变量
是可以的
这点在c++之类的也非常常见
这个就叫协变
let animal_ins=new dog()
总结一句话就是
由子类变成父类就叫协变
逆变
逆变是与协变相反的
是由父类变成子类
关于这点我们可以看一个demo
我们假设两个函数
handle_animal类型是(animal)=>void
handle_dog的类型是(dog)=>void
大家可以猜一下这两个相互赋值的结果
结果是handle_animal可以赋值给handle_dog
而handle_dog不可以赋值给handle_animal
为什么?
我们可以想一下
我们传递给变量的是一个函数的引用
在赋值的时候会检查两边的参数类型
可以知道handle_animal要求传入一个animal
而handle_dog要求传入一个dog
animal是dog的父类
可以知道animal其所需的数据一定可以由dog满足
所以可以赋值
而如果使用了
handle_dog赋值给handle_animal
我们的handle_animal的类型限制是animal
函数却要求一个dog
animal无法满足其充当dog的数据
所以无法赋值
总而言之
即在一个父子继承的过程中
所需的数据可以被满足
即可正常赋值
怎么看出来的逆变
我们将handle_animal的函数赋值给handle_dog后
虽然函数已经进行了赋值
但是其类型依然是dog类型
说明传入的参数由要求父类型变成了要求子类型
即父类变子类
称之为逆变
而在函数返回类型的过程中
可以将函数返回的子类类型赋值给父类类型变量
因为由子类变父类
称之为协变
所以官方才说
参数是逆变的
返回是协变的
双向协变
在早期ts版本中
其参数既可以逆变
又可以协变
即称之为双向协变
但是在参数中协变是不安全的
如
let func:(animal)=>void=function (dog:dog){dog.eat_shit()}
在早期的版本是可以正常通过类型检查的
但是animal的数据不足以支撑dog类型的一些特殊属性
所以容易引发报错
如这个demo
enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));
我们可以看到
(MouseEvent)=>void赋值给(Event)=>void失败了
因为子类型想变化成父类型
属于一个类型的协变
而参数在当前版本协变已经不允许了
所以报错
结语
ts好难啊...