精进TypeScript--理解代码的生成是独立于类型的

本文解释了TypeScript编译器如何将类型安全代码转换为JavaScript,同时强调类型错误不影响代码的运行,但会影响代码生成。文章探讨了如何在运行时处理类型、性能影响以及利用类型系统的优势和限制。
摘要由CSDN通过智能技术生成

tsc(TypeScript编译器)做了两件事:
1.它将下一代 TypeScript 转换为可以在浏览器中正常工作的旧版 JavaScript
2.它会检查你的代码是否有类型错误
令人惊讶的是,这两个行为完全是相互独立的。代码中的类型不能影响 TypeScript 生成 JavaScript。由于被执行的是 JavaScript,这意味着类型不会影响代码运行方式。

要记住的事:

  • 代码的生成是独立于类型系统的。意味着 TypeScript 类型不能影响代码的运行时行为或性能。
  • 一个有类型错误的程序也有可能产生代码的(“编译”)。
  • TypeScript 类型在运行时是不可用的。要在运行时查询一个类型,你需要一些方法来重建它。标签联合和属性检查是常见的方法。一些构造,如class,既引入了 TypeScript 类型,有引入了一个在运行时可用的值。

类型错误的代码仍会产生输出

由于代码输出与类型检查无关,因此有类型错误的代码也可以产生输出:

// cat test.ts
let x = 'hello';
x = 1234;
// tsc test.ts  // ~~ test.ts:2:1 TS2322: Type 'number' is not assignable to type 'string'.
// cat test.js 编译生成test.js
var x = 'hello';
x = 1234;

在存在错误的情况下仍输出代码是有实际帮助的。如果你正在构建一个 Web 应用程序,你可能知道它的某个部分有问题。但是因为 TypeScript 仍然会在存在错误的情况下生成代码,你可以在修复这些错误之前测试你的应用程序的其他部分。

当你提交代码时,应该以零错误为目标。如果你想禁用错误输出,你可以使用 tsconfig.json 中的 noEmitOnError 选项,或者在你的构建工具中使用等效的选项。

你无法在运行时检查 TypeScript 类型

看下代码:

interface Square {
	width: number;
}
interface Rectangle extends Square {
	height: number;
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
	if (shape instanceif Rectangle) { // ~~ 'Rectangle' 仅代表的是类型,但在这里被用作一个值
		return shape.width * shape.height; // ~~ 类型 “Shape” 上不存在属性 “height”
	} else {
		return shape.width * shape.width;
	}
}

instanceof 检查是在运行时发生的,但 Rectangle 却是一个类型,所以它无法影响代码的运行时行为。TypeScript 类型都是 “可擦除” 的:编译成 JavaScript 的过程,其中一部分就是简单地从你的代码中删除所有的 interface、type 和类型注释。可以这么检查:

function calculateArea(shape: Shape) {
	if ('height' in shape) { // 类型是 Rectangle
		return shape.width * shape.height;
	} else {
		return shape.width * shape.width;
	}
}

这是因为属性检查只涉及运行时可用的值,但仍然允许类型检查器将类型 shape 细化为 Rectangle 类型。

还有一种方法是引入一个标签,显式地将类型保留到运行时可用:

interface Square {
	kind: 'square';
	width: number;
}
interface Rectangle {
	kind: 'rectangle';
	height: number;
	width: number;
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
	if (shape.kind === 'rectangle') { // 类型是 Rectangle
		return shape.width * shape.height;
	} else {
		return shape.width * shape.width;
	}
}

这里的 Shape 类型就是一个 “标签联合类型” 的例子。因为它们可以非常容易地在运行时恢复类型信息,所以标签联合在 TypeScript 中无处不在。

还有一种修复错误的方法是可以将 Square 和 Rectangle 作为类。因为 class 会同时引入一个类型(在运行时不可用)和一个值(在运行时可用)。

class Square {
	constructor(public width: number) {}
}
class Rectangle extends Square {
	consctuctor(public width: number, public height: number) {
		super(width);
	}
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
	if (shape instanceif Rectangle) { // 类型是 Rectangle
		return shape.width * shape.height;
	} else {
		return shape.width * shape.width;
	}
}

这个例子中,type Shape = Square | Rectangle 中的 Rectangle 指的是类型,而 shape instanceif Rectangle 中的 Rectangle 指的是值。尽管这种区别相当微妙,但对它的理解非常重要。

类型操作不能影响运行时的值

比如你有一个可能是字符串或数字的值,想把它正常化,使其总是一个数字。一下是一个错误的例子,尽管类型检查器接受了。

function asNumber(val: number | string): number {
	return val as number;
}
// 看下生成的 JavaScript
function asNumber(val) {
	reuturn val;
}

这里根本就没有进行任何转换。as number 是一个类型操作(类型断言),所以它无法影响你代码的运行时行为。为了使一个值正常化,你需要检查它的运行时类型:

function asNumber(val: number | string): number {
	return typeof(val) === 'string' ? Number(val) : val;
}

TypeScript 类型对运行时的性能没有影响

因为当你生成 JavaScript 时,类型和类型操作会被清除,所以它们不可能对运行时性能产生影响。TypeScript 的静态类型是真正的零成本。

需要注意两个事项:

  • 虽然没有运行时开销,但 TypeScript 编译器会引入构建时的开销。TypeScript 团队非常重视编译器的性能,编译速度通常相当快,特别是对于增量构建。如果开销变得显著,你也可以使用 “tanspile ony” 选项跳过类型检查。
  • TypeScript 为支持旧的运行时而产生的代码可能会对原生实现产生性能开销。例如,如果你使用生成器函数,并以 ES5 为目标,而 ES5 比生成器出现的更早,那么 tsc 就会产生一些辅助代码来使其工作,这与生成器的原生实现相比,可能会有一些开销。
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~卷心菜~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值