结构型类型(“鸭子类型”)
TypeScript 类型兼容性是基于结构类型的;结构类型只使用其成员来描述类型。 类型检查关注的是值的形状, 即鸭子类型
。而且一般通过interface定义类型,其实就是定义形状与约束。 所以定义interface其实是针对结构来定义新类型。
对于Typescript来说,两个类型只要结构相同,那么它们就是同样的类型。简单来说就是 如果x要兼容y,那么y至少具有与x相同的属性。
比如:
interface Named {
name: string;
}
let x: Named;
let y = { name: 'xman', age: 18};
x = y;
这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是name的string类型成员。y满足条件,因此赋值正确。
以下可以赋值成功吗?
interface IPerson {
name: string;
age: number;
}
let p = {
name: 'xman',
age: 18,
height: '60kg'
}
let person: Person = p; // 没毛病
按照规则, 接口IPerson中每一个属性在 p 对象中都能找到对应的属性,且类型匹配。 另外, 可以看到 p 对象具有一个额外属性height, 但是赋值同样会成功。
附带说下结构型(structural)类型
以及名义型(nominal)类型
- 名义类型是静态语言Java、C等语言所使用的,简单来说就是,如果两个类型的类型名不同,那么这两个类型就是不同的类型了,尽管两个类型是相同的结构
- 结构型类型中的类型检测和判断的依据是类型的结构,会看它有哪些属性,分别是什么类型;在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的
- Typescript中的类型是结构型类型, 类型检查关注的是值的形状, 两个类型只要结构相同,那么它们就是同样的类型
如:
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
p = new Person(); // 没毛病
在使用基于名义类型的语言,比如C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。
在TypeScript中, 因为Person类与Named接口具有相同的结构,所以它们是互相兼容的。 因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。
Freshness 特性
如上所述, 只要满足结构类型兼容规则
的两个类型便可相互兼容。那是否有例外呢?
interface Named {
name: string;
}
interface IPerson {
name: string;
age: number;
}
let p: Name;
p = {name: 'xman', age: 18}
// Type '{ name: string; age: number; }' is not assignable to type 'Named'.
// Object literal may only specify known properties, and 'age' does not exist in type 'Named'.
提示 不能将类型'{ name: string; age: number; }'分配给类型'Named'。 对象字面量只能指定已知的属性,并且在“Named”类型中不存在“age”
。
上述代码中, 虽然为变量p
赋予的对象字面量完全符合结构类型兼容规则
, 但是它却抛出错误, 这主要是由TS中的Freshness特性导致的。
[Freshness]
特性会对对象字面量
进行更为严格的类型检测: 只有目标变量的类型与该对象字面量的类型完全一致时, 对象字面量才可赋值给目标变量,否则将抛出类型错误。
我们可以通过以下方法进行消除编译异常:
let p: Name;
p = {name: 'xman', age: 18} as IPerson;
或者:
let p: Name;
let person: IPerson = {name: 'xman', age: 18};
p = person;
Freshness缺点: 它能误导你认为某些东西接收的数据比它实际的多
function logName(something: { name: string }) {
console.log(something.name);
}
logName({ name: 'matt' }); // ok
logName({ name: 'matt', job: 'being awesome' });
// Argument of type '{ name: string; job: string; }' is not assignable to parameter of type '{ name: string; }'.
// Object literal may only specify known properties, and 'job' does not exist in type '{ name: string; }'.
注意:
这种错误提示,只会发生在对象字面量上。