TypeScript 类型兼容深层逻辑
TypeScript 使用结构化类型系统,类型兼容性取决于结构而非名称,下面梳理深层兼容规则。
结构化类型 vs 名义类型
结构化类型(TypeScript)
TypeScript
interface A { x: number; y: string; }
interface B { x: number; y: string; }
let a: A = { x: 1, y: "hello" };
let b: B = a; // ✅ 结构相同,可赋值
名义类型(Java/C#)
Java
// Java 中即使结构相同,不同类型也无法赋值
interface A { int x; String y; }
interface B { int x; String y; }
// A a = new B(); // ❌ 类型不同
对象兼容性规则
多余属性检查
仅在字面量赋值时生效:
TypeScript
interface User { name: string; }
// 字面量直接赋值:严格检查
const u: User = { name: "Alice", age: 25 }; // ❌ age 不存在
// 通过变量赋值:允许
const data = { name: "Alice", age: 25 };
const u2: User = data; // ✅ 结构兼容(目标有更多属性可以)
子类型兼容
目标类型的每个属性在源类型中都必须存在且兼容:
TypeScript
interface Base { id: string; }
interface Extended extends Base { name: string; }
let base: Base;
let extended: Extended = { id: "1", name: "Alice" };
base = extended; // ✅ Extended 包含 Base 所有属性
// extended = base; // ❌ Base 缺少 name
函数兼容性
参数数量
源函数参数可以多于目标函数,但不能少于:
TypeScript
let fn1 = (a: number, b: number) => {};
let fn2 = (a: number) => {};
fn1 = fn2; // ✅ 目标参数少可以(忽略多余参数)
// fn2 = fn1; // ❌ 源参数多,调用时可能缺少参数
参数类型协变与逆变
TypeScript 默认函数参数双向协变(strictFunctionTypes 开启后变为逆变):
TypeScript
// 开启 strictFunctionTypes 后
class Animal { run() {} }
class Dog extends Animal { bark() {} }
let fn1 = (a: Animal) => {};
let fn2 = (a: Dog) => {};
fn1 = fn2; // ❌ Dog 是 Animal 子类型,参数应逆变
fn2 = fn1; // ✅ 接受 Animal 可以传入 Dog
返回值协变
返回值必须是目标返回值的子类型:
TypeScript
class Animal {}
class Dog extends Animal {}
let fn1 = () => new Animal();
let fn2 = () => new Dog();
fn1 = fn2; // ✅ Dog 是 Animal 子类型
// fn2 = fn1; // ❌ Animal 不是 Dog 子类型
泛型兼容性
类型参数兼容
TypeScript
interface Container<T> {
value: T;
}
let c1: Container<string | number>;
let c2: Container<string>;
c1 = c2; // ✅ string 是 string|number 子类型
// c2 = c1; // ❌ string|number 不一定是 string
泛型函数兼容
TypeScript
type Fn<T> = (arg: T) => T;
let f1: Fn<string | number>;
let f2: Fn<string>;
f1 = f2; // ✅(返回值协变,参数逆变)
枚举兼容性
枚举与数字双向兼容:
TypeScript
enum Status { Active = 1, Inactive = 2 }
let s: Status = Status.Active;
let n: number = 1;
s = n; // ✅ 数字可赋值给枚举
n = s; // ✅ 枚举可赋值给数字
不同枚举间不兼容:
TypeScript
enum Color { Red = 0, Green = 1 }
enum Size { Small = 0, Large = 1 }
let c: Color = Color.Red;
// let sz: Size = c; // ❌ 不同枚举不兼容
要点总结
- TypeScript 使用结构化类型,结构相同即可赋值,不关心名称
- 字面量赋值时有多余属性检查,变量赋值时不检查
- 函数参数默认协变(
strictFunctionTypes开启后逆变),返回值协变 - 泛型兼容性取决于类型参数的协变/逆变声明
- 枚举与数字双向兼容,不同枚举间不兼容
📝 发现内容有误?点击此处直接编辑