李恒道 发表于 2022-6-26 17:43:59

typescript 协变与逆变与双向协变

# 前文
最近在学习ts
好多乱七八糟的概念都不懂
一之哥耐心辅导了两天才明白了一个大概概念
记录一下
# 协变
我们可以假设一个
class animal{
      xxxx
}
class dog extends animal{
      xxxx
}
这个时候animal是dog的父类
如果我们将子类的实例赋值给数据类型为父类的变量
是可以的
这点在c++之类的也非常常见
这个就叫协变
let animal_ins=new dog()
总结一句话就是
由子类变成父类就叫协变
# 逆变
逆变是与协变相反的
是由父类变成子类
关于这点我们可以看一个demo
我们假设两个函数
![图片.png](data/attachment/forum/202206/26/173018dcuzwwucyoxlowct.png)
handle_animal类型是(animal)=>void
handle_dog的类型是(dog)=>void
大家可以猜一下这两个相互赋值的结果
![图片.png](data/attachment/forum/202206/26/173113tcya3gxpxc2pdj2s.png)
结果是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类型
![图片.png](data/attachment/forum/202206/26/173632fwd6i0worrwtnitr.png)
说明传入的参数由要求父类型变成了要求子类型
即父类变子类
称之为逆变
而在函数返回类型的过程中
可以将函数返回的子类类型赋值给父类类型变量
因为由子类变父类
称之为协变
所以官方才说
参数是逆变的
返回是协变的
# 双向协变
在早期ts版本中
其参数既可以逆变
又可以协变
即称之为双向协变
但是在参数中协变是不安全的

let func:(animal)=>void=function (dog:dog){dog.eat_shit()}
在早期的版本是可以正常通过类型检查的
但是animal的数据不足以支撑dog类型的一些特殊属性
所以容易引发报错
如这个demo
```javascript
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));
```
![图片.png](data/attachment/forum/202206/26/174243y3dduxqp1x4fuo4d.png)
我们可以看到
(MouseEvent)=>void赋值给(Event)=>void失败了
因为子类型想变化成父类型
属于一个类型的协变
而参数在当前版本协变已经不允许了
所以报错
# 结语
ts好难啊...

wwwwwllllk 发表于 2022-6-26 20:16:04

感觉就是强转类型呀。我也不懂协变第一次听

李恒道 发表于 2022-6-26 20:57:30

wwwwwllllk 发表于 2022-6-26 20:16
感觉就是强转类型呀。我也不懂协变第一次听

没错
就是函数类型转换的规定~

王一之 发表于 2022-6-26 22:48:02

学习了,感谢楼主分享

李恒道 发表于 2022-6-27 09:26:31

王一之 发表于 2022-6-26 22:48
学习了,感谢楼主分享

mhsj教mhsj代码?
页: [1]
查看完整版本: typescript 协变与逆变与双向协变