条款三:理解代码的生成是独立于类型的
首先理解转译和编译的概念:
- 转译指的是以某种变成语言的程序作为输入,生成由另一种编程语言构成的等效代码的过程;
- 编译指的是从一个较为高级的语言转换成低级语言;
TS 编译器做了两件事情:
- 将 TS 代码转换成可以在浏览器中可以运行的 JS 代码(类型擦除);
- 转译时,检查代码是否有类型错误(类型检查);
类型错误不会影响编译产出:
在 TS => JS 的转译过程中,TS 编译器会将 TS 代码中的类型擦去,使其成为能够浏览器可运行的 JS 代码;所以类型错误的代码仍然会产生输出,如图所示:
你无法在运行时检查 TS 类型(使用 TS 的类型作为一个值)
因为在运行时,目前只有浏览器环境和 Node 环境能够运行 JS 代码,所以 TS 代码只有在转译成 JS 代码之后才能被运行,但是在转译的过程中,一部分工作就是直接擦除 TS 代码中的类型;
举例:求长方形和正方形的面积:
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
因为 instanceof 检查是发生在运行时,但是 Rectangle 确实一个类型,他无法影响代码运行时的行为,即在代码运行的时候,类型已经被擦去了,所以此时代码会爆出错误;
这个时候,可以使用“标签联合类型”来解决,即在类型中,添加一个自身独有的类型标签,可以在运行时回复类型信息,注意,只是恢复,他不会影响运行的任何代码:
interface Square {
kind: "square";
width: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape.kind === "rectangle") {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
有一些构造过程会同时引入一个类型(运行时不可用)和一个值(运行时可用)。class 关键字就是其中一个,上述例子也可通过 class 实现 Square 和 Rectangle 两个类,然后可以通过 instanceof Square 来判断,此时的 Square 是一个值;
类型操作不能影响运行时的值
比如说你先把 一个入参可能是字符串也可能是数字的值始终使其成为数字,错误的写法如下:
function asNumber(val: number | string) {
return val as number;
}
// tsc 后
function asNumber(val) {
return val
};
TS 类型对运行时的性能没有影响
因为生成 JS 代码是,类型和类型操作都会被清除,所以他们不可能对运行时的性能产生影响;TS 的静态类型是真正的零成本;
- 虽然没有运行时的开销,但是 TS 编译器会引入构建时的开销;
总结:
- 代码生成是独立于类型系统的,即 TS 类型不能影响运行时的行为和性能;
- 一个有类型错误的 TS 程序可以编译成 JS 程序;
- TS 类型运行时不可用,运行时如果想要判断一个类型,可以使用标签联合等方法;
- class 关键字即引入了一个 TS 类型,又引入了一个在运行时可用的值;