You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何TypeScript中函数参数具有逆变特性?

函数参数逆变的原理:从类型安全出发的必然规则

先拿具体的类型例子拆解,避免抽象定义的迷惑:

class Animal {
  eat() {}
}

class Dog extends Animal {
  bark() {}
}

这里Dog是Animal的子类型(Dog extends Animal),对应你说的T extends U(T=Dog,U=Animal)。

先看协变(做对比)

协变多见于产出数据的类型,比如数组:Dog[]可以赋值给Animal[]。因为从数组里拿出来的都是Dog,肯定能当Animal用,不会出问题,完全符合你说的协变规则:T extends U → G<T> extends G<U>(G是数组类型)。

函数参数为什么是逆变?

函数是接收输入、返回输出的逻辑块,参数属于「输入」位置,类型规则必须保证:赋值后的函数调用不会触发类型错误。

定义两个函数类型:

type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;

按照逆变规则,因为Dog extends Animal,所以AnimalHandler可以赋值给DogHandler类型的变量——这看起来反直觉,但实际调用是安全的:

// 把AnimalHandler赋值给DogHandler变量
let handler: DogHandler = (animal: Animal) => {
  animal.eat(); // 只用到Animal的公共方法
};

// 调用时传入Dog,完全没问题
handler(new Dog());

函数只依赖Animal的属性,Dog作为子类型肯定具备这些属性,所以调用安全。

如果反过来,允许把DogHandler赋值给AnimalHandler:

let handler: AnimalHandler = (dog: Dog) => {
  dog.bark(); // 调用Dog特有的方法
};

// 调用时传入Animal,直接报错
handler(new Animal()); // Animal没有bark方法

这就直接破坏了类型安全,所以这种赋值是不被允许的——这就是逆变规则存在的核心原因:保证函数调用时,参数的实际类型不会超出函数能处理的范围。

本质:输入输出的方差逻辑

函数类型的方差规则是行业通用的(不止TypeScript,Java、C#等都遵循):

  • 返回值(输出位置)是协变的:比如() => Dog可以赋值给() => Animal,因为返回的Dog能当作Animal用,和数组协变逻辑一致。
  • 参数(输入位置)是逆变的:接受更宽泛类型的函数,必然能处理更具体的子类型输入,不会触发错误。

这本质是对「里氏替换原则」的延伸:子类型可以替换父类型,但处理父类型的逻辑,一定能兼容子类型的输入。

内容的提问来源于stack exchange,提问作者LeoPkm2-1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:37:44