《Effective TypeScript 》-- 条款3: 理解代码的生成是独立于类型的

88 阅读3分钟

条款三:理解代码的生成是独立于类型的

首先理解转译和编译的概念:

  • 转译指的是以某种变成语言的程序作为输入,生成由另一种编程语言构成的等效代码的过程;
  • 编译指的是从一个较为高级的语言转换成低级语言;

TS 编译器做了两件事情:

  • 将 TS 代码转换成可以在浏览器中可以运行的 JS 代码(类型擦除);
  • 转译时,检查代码是否有类型错误(类型检查);

类型错误不会影响编译产出:

在 TS => JS 的转译过程中,TS 编译器会将 TS 代码中的类型擦去,使其成为能够浏览器可运行的 JS 代码;所以类型错误的代码仍然会产生输出,如图所示:

f1409da3-2dce-49c7-8a10-9286b33e120a.png

你无法在运行时检查 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 确实一个类型,他无法影响代码运行时的行为,即在代码运行的时候,类型已经被擦去了,所以此时代码会爆出错误;

image.png

这个时候,可以使用“标签联合类型”来解决,即在类型中,添加一个自身独有的类型标签,可以在运行时回复类型信息,注意,只是恢复,他不会影响运行的任何代码:

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 类型,又引入了一个在运行时可用的值;