Если вы работаете с JavaScript, то, кончечно, знаете, что есть несколько способов объявить переменную. Сегодня мы обычно пишем так:
const password = "hunter2"
и лишь изредка, когда нужно что-то изменяемое:
let password = "hunter2"
Эти объявления существуют уже давно и имеют адекватные правила блочной области видимости:
function example(measurement) {
console.log(calculation); // ReferenceError
console.log(anotherCalc); // ReferenceError
if (measurement > 1) {
const calculation = measurement + 1;
let anotherCalc = measurement * 2;
// ...
} else {
// ...
}
console.log(calculation); // ReferenceError
console.log(anotherCalc); // ReferenceError
}
Вы, возможно, также помните времена, когда такие объявления не были доступны. У нас был только var
. А 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;
// Current source map file and its index in the sources list
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 = "";
// Last recorded and encoded mappings
var lastGeneratedLine = 0;
var lastGeneratedCharacter = 0;
var lastSourceIndex = 0;
var lastSourceLine = 0;
var lastSourceCharacter = 0;
var lastNameIndex = 0;
var hasLast = false;
// ... etc
}
Причина кроется в так называемых Зонах временной недоступности (Temporal Dead Zone, aka TDZ, aka Временные мертвые зоны, согласно русской документации MDN). Для каждой переменной есть зона, где она объявлена, но ещё не инициализирована. Следующий пример это наглядно показывает:
function example() {
const result = Math.random() < 0.5 ? useX() : 1; // 50% шанс ReferenceError
const x = 10;
return result;
function useX() {
return x;
}
}
В этом примере совершенно нормально объявить useX
внизу функции. Проблема появляется, если вызвать её до инициализации x
- то есть пока интерпретатор ещё находится в TDZ для 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
Ужас!
Таким образом, TDZ - это очень полезная фича, появившаяся вместе с const
и let
.
Так почему же TypeScript не хочет её использовать? Всё дело в производительности!
Определить, находится ли интерпретатор в TDZ переменной, - это тяжёлая работа. Как видно выше, сделать это статически нельзя: результат зависит от недетерминированного поведения во время исполнения. Это накладывает ощутимые издержки, которые сильно влияли на производительность кода TypeScript. После того как они переписали значительное количество объявлений на var
, получили улучшение производительности на 8% в некоторых бенчмарках.
Что касается меня, я очень рад, что в повседневной разработке больше не нужно писать var
. А для TypeScript это, похоже, ещё одна причина переписать кодовую базу на Go.