Если вы работаете с 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.