类型兼容性是指判断一个类型是否能够被赋值给另一个类型。TypeScript 采用结构化类型系统,也被称作鸭子类型。这种类型系统的核心理念是:如果一个对象具备目标类型的所有属性和方法,那么它就与目标类型兼容,无论类型的名称是否一致。
结构化类型系统只根据类型的结构(属性、方法) 判断兼容性,完全忽略类型的声明名称,这是 TypeScript 最核心的类型规则。
class A {
name: string;
age: number;
}
class B {
name: string;
age: number;
}
// 两个类名称不同,但结构完全一致,兼容赋值
const a: A = new B();
const b: B = new A();在这个例子中,A 和 B 是两个独立的类,名称完全不同,但它们拥有相同的属性结构。TypeScript 判定二者类型兼容,因此可以互相赋值,不会抛出类型错误。
标称类型系统是基于类型名称进行检查的类型系统,主流语言如 Java、C# 都采用这种规则。 即使两个类型结构完全相同,只要名称不同,就会被判定为不兼容。
// Java 标称类型系统示例
class A {
String name;
int age;
}
class B {
String name;
int age;
}
// 编译报错:结构相同但名称不同,不兼容
A a = new B();在标称类型系统中,类型的身份由名称唯一确定,结构一致无法作为兼容的依据,这与 TypeScript 的规则完全相反。
接口兼容性遵循最小满足原则:源类型必须包含目标类型的所有成员,源类型可以拥有多余的成员,不影响兼容性。 该规则同时适用于对象字面量、接口、类之间的赋值。
interface A {
name: string;
age: number;
}
interface B {
name: string;
}
class C {
name: string;
age: number;
sex: string;
}
let a: A = { name: '刃', age: 18 };
// 兼容:A 包含 B 的所有属性,满足 B 的要求
let b: B = a;
// 兼容:类 C 的实例满足接口 B 的结构要求
let c: B = new C();核心规则
目标类型(如接口 B)定义了所需的最小结构
源类型只要包含目标类型的所有成员,就可以赋值
源类型的多余属性会被忽略,不影响兼容性
TypeScript 中基础数据类型的兼容性规则最简单:类型完全一致才兼容。
let str: string = "hello";
let num: number = 123;
str = num; // 报错:string 和 number 类型不兼容
num = 456; // 正确:同类型兼容赋值特殊情况:null 和 undefined 可以赋值给任意类型(关闭严格空检查时)。
函数是 TypeScript 中特殊的类型,兼容性遵循两条核心规则:
参数个数:源函数的参数个数 小于等于 目标函数,兼容
返回值类型:源函数返回值满足目标函数要求,兼容
// 目标函数:2 个参数,无返回值
type Fn1 = (a: number, b: number) => void;
// 源函数:1 个参数,无返回值
type Fn2 = (a: number) => void;
const fn: Fn1 = (a) => {}; // 正确:参数少的兼容参数多的结构化类型系统有一个例外: 如果类中包含 private(私有) 或 protected(受保护) 成员,那么只有来自同一个类的实例才兼容。
class X {
private name: string;
}
class Y {
private name: string;
}
const x: X = new Y(); // 报错:私有成员来自不同类,不兼容核心规则:TypeScript 是结构化类型系统,结构决定兼容性,与类型名称无关;
赋值原则:源类型包含目标类型的所有成员,即可兼容赋值,多余属性不影响;
特殊场景:基础类型必须完全一致,类的私有 / 受保护成员会破坏结构兼容性。