原标题:The Temporal Dead Zone, or why the TypeScript codebase is littered with var statements
原文地址:vincentrolfs.dev/blog/ts-var
译者声明:为了让译文流畅、得体,译者调整了部分句子的人称、语序和表达方式。对有官方中文译文或具备官方性质的中文译文的原文链接,已进行替换。译文不代表、不暗示译者观点和/或立场
凡是使用过 JavaScript 一段时间的人,都知道初始化变量有几种不同方法。如今我们通常用
const password = "hunter2"
偶尔,我们需要一些可变状态:
let password = "hunter2"
(业界)这么声明(变量)已经好长时间了,这样做(让变量)符合合理的作用域规则:
function example(measurement) {
console.log(calculation); // 引用错误
console.log(anotherCalc); // 引用错误
if (measurement > 1) {
const calculation = measurement + 1;
let anotherCalc = measurement * 2;
// ...
} else {
// ...
}
console.log(calculation); // 引用错误
console.log(anotherCalc); // 引用错误
}
然而,从泥盆纪混到现在的前辈可能还记得只能用 var 的时代。用 var 可是够遭罪的。这玩意儿不仅是可变的,想要弄成不可变的一点儿办法也没有。而且它还会从(大括弧包裹的)作用域中泄露出去。
function example(measurement) {
console.log(calculation); // undefined - 能访问到! calculation 泄漏
console.log(i); // undefined - 能访问到! i 泄漏
if (measurement > 1) {
var calculation = measurement + 1;
// ...
} else {
// ...
}
console.log(calculation); // 1 - 能访问到! calculation 泄漏
for (var i = 0; i < 3; i++) {
// ...
}
console.log(i); // 3 - 能访问到! i 泄漏
}
忒可怕了!
笔者看到 TypeScript 代码库(现在就是 TypeScript 开发的)里满天飞的 var 语句,也吓了一激灵。(它怎么就)跟2003年写成的一样:
/** @internal */
export function createSourceMapGenerator(
host: EmitHost,
file: string,
sourceRoot: string,
sourcesDirectoryPath: string,
generatorOptions: SourceMapGeneratorOptions
): SourceMapGenerator {
/* eslint-disable no-var */
var { enter, exit } = generatorOptions.extendedDiagnostics
? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap")
: performance.nullTimer;
// 当前源映射文件及其在源列表中的索引
var rawSources: string[] = [];
var sources: string[] = [];
var sourceToSourceIndexMap = new Map<string, number>();
var sourcesContent: (string | null)[] | undefined; // eslint-disable-line no-restricted-syntax
var names: string[] = [];
var nameToNameIndexMap: Map<string, number> | undefined;
var mappingCharCodes: number[] = [];
var mappings = "";
// 最终记录和编码的映射关系
var lastGeneratedLine = 0;
var lastGeneratedCharacter = 0;
var lastSourceIndex = 0;
var lastSourceLine = 0;
var lastSourceCharacter = 0;
var lastNameIndex = 0;
var hasLast = false;
// ... 其他代码
}
要是没听说过暂时性死区的话,可能还不知道为啥呢。(大概意思就是)代码里的每个变量都有一个已声明未初始化的区域。直接看(代码)示例可能更清楚:
function example() {
const result = Math.random() < 0.5 ? useX() : 1; // 有五成概率抛 ReferenceError
const x = 10;
return result;
function useX() {
return x;
}
}
在上面的例子中,把 useX (函数)声明在函数末尾是完全有效的。问题出现在 x 尚未初始化时就调用它。说白了,当解释器仍处于 x 的暂时性死区时,解释器会拒绝我们访问 x 并抛出错误。
这其实贼棒!因为如果这个例子中使用 var 而非 const,就不会报错。函数只会简单地返回 undefined!
function example() {
var result = useX(); // undefined
var x = 10;
return result; // undefined
function useX() {
return x;
}
}
console.log(example()); // undefined
忒可怕了!
综上所述,暂时性死区实际上是 const 和 let 带来的贼好用的特性。那 TypeScript 凭啥不乐意使呢?原因在于性能。
就为判断变量是不是在暂时性死区,解释器得耗贼多资源。我们上文讲过,这种判断无法通过静态分析完成,而是依赖于非确定性的运行时行为。这些损耗对 TypeScript 代码库影响显著。把大量声明语句迁移至 var 后,部分基准测试呈现出8%的性能提升。
就笔者来说,非常庆幸现已无需使用 var 了。而对 TypeScript(团队)来说,想必又给迁移至 Go 添了条理由。