类型系统
一组为变量、函数等结构分配、实施类型的规则,通过显式地指定或类型推导来分配类型。同时类型系统也定义了如何判断类型之间的兼容性:在 TypeScript 中即是结构化类型系统。
结构化类型系统
typescrpt 比较两个类型并非通过类型的名称,而是比较这两个类型上实际拥有的属性与方法:
ts
class Cat{
move () {}
}
class Dog{
move () {}
}
const mm: Cat = new Dog()
结构化类型有个别称叫鸭子类型。即如果你看到一只鸟走起来像鸭子,游泳像鸭子,叫得也像鸭子,那么这只鸟就是鸭子。
如果在 Dog 上添加一个独有成员,也不会报错。因为结构化类型系统会认为 Dog 类完全实现了 Cat 类。
ts
class Cat{
move () {}
}
class Dog{
move () {}
eat () {}
}
const mm: Cat = new Dog()
标准类型系统
标准类型系统是指类型系统会根据类型名称进行比较。如果类型名称不同,即使类型上的属性与方法完全一致,也会报错。C++、Java、Rust 等语言中都主要使用标称类型系统。
但是在 ts 中,无法直接实现:
ts
type USD = number;
type CNY = number;
function addMoney (usd: USD, cny: CNY) {
return usd + cny
}
const usd: USD = 100
const cny: CNY = 200
addMoney(cny, usd) // success
在 TypeScript 中模拟标称类型系统
类型的重要意义之一是限制了数据的可用操作与实际意义。这往往是通过类型附带的额外信息来实现的(类似于元数据),要在 TypeScript 中实现,其实我们也只需要为类型额外附加元数据即可。
就是,给类型打个 tag。
ts
export declare class TagProtector<T extends string>{
protected __tag__: T
}
export type Nominal<T, U extends string> = T & TagProtector<U>;
用TagProtector
声明一个带 tag 的类,然后用Nominal
将类型与 tag 关联起来。这样,我们就可以通过Nominal
来模拟标称类型系统了。
ts
type USD = Nominal<number, 'usd'>;
type CNY = Nominal<number, 'cny'>;
const usd: USD = 100 as USD
const cny: CNY = 200 as CNY
function addMoney (usd: USD, cny: CNY) {
return (usd + cny) as CNY
}
addMoney(cny, usd)
addMoney(usd, cny) // success
或者,在类上打 tag,可以在运行时添加更多的检查逻辑,同时在类型层面也得到了保障。
typescript
class USD {
private __tag!: void;
constructor(public value: number) {}
}
class CNY {
private __tag!: void;
constructor(public value: number) {}
}
const usd = new USD(100);
const cny = new CNY(100);
function addMoney(usd: USD, cny: CNY) {
return usd.value + cny.value;
}
addMoney(cny, usd);
addMoney(usd, cny); // success