Learning Book

TDZ: временная мёртвая зона

Что такое TDZ

Temporal Dead Zone (временная мёртвая зона) — промежуток времени от начала скоупа до строки объявления let/const, в течение которого обращение к переменной вызывает ReferenceError.

// Начало скоупа → переменная x в TDZ

console.log(x) // ReferenceError: Cannot access 'x' before initialization
               // ← x существует, но недоступна (в TDZ)

let x = 5     // ← x выходит из TDZ, инициализируется значением 5

console.log(x) // 5 ✓
Важное уточнение: переменная let/const **известна** JavaScript с начала скоупа (имя зарегистрировано в Environment Record), но находится в специальном «мёртвом» состоянии. Это не то же самое, что var с undefined.

typeof в TDZ

Обычно typeof не выбрасывает ошибку даже для несуществующих переменных. Но в TDZ — выбрасывает:

// Необъявленная переменная — typeof безопасен
console.log(typeof undeclaredVar) // "undefined" (не ошибка)

// Переменная в TDZ — typeof тоже бросает ReferenceError!
console.log(typeof myVar) // ReferenceError!
let myVar = 42

Это ломает старый паттерн проверки существования переменной через typeof:

// Старый паттерн (работал с var)
if (typeof myFeature !== 'undefined') {
  // использовать myFeature
}

// Если myFeature объявлена через let/const ниже — ReferenceError!
// Решение: переносить все объявления в начало скоупа

Почему TDZ существует

TDZ — это намеренное дизайнерское решение, а не случайность. Причины:

1. Предотвращение ошибок с var

С var было легко допустить баг: переменная «существует» с начала функции как undefined, и ошибки нет. TDZ делает такие ошибки явными.

// var — молча возвращает undefined, скрывает баг
function withVar() {
  console.log(count) // undefined — не заметишь проблему
  var count = 0
}

// let — явно говорит: "ты обратился до инициализации"
function withLet() {
  console.log(count) // ReferenceError — сразу видно проблему
  let count = 0
}

2. Поддержка const

const не может быть инициализирован как undefined — у него нет «неинициализированного» состояния. TDZ позволяет const и let вести себя одинаково.

3. Будущие оптимизации движка

TDZ позволяет движкам JavaScript хранить let/const на стеке (не в куче), потому что нет нужды эмулировать «undefined до инициализации».

TDZ в классах

TDZ применяется и к классам:

// Нельзя использовать класс до его объявления
const obj = new MyClass() // ReferenceError: Cannot access 'MyClass' before initialization

class MyClass {
  constructor() {
    this.value = 42
  }
}

В отличие от функций, class не поднимается полностью.

TDZ в параметрах по умолчанию

Параметры по умолчанию вычисляются слева направо — ранний параметр не может ссылаться на поздний:

// Ошибка: b ссылается на a, которая уже инициализирована — ок
// Но a ссылается на b, которая ещё в TDZ — ошибка
function test(a = b, b = 1) { // ReferenceError
  return a + b
}

// Правильно: ранний параметр ссылается только на предыдущие
function test(a = 1, b = a + 1) { // ок
  return a + b
}
test() // 3
Технически в спецификации (ECMA-262) состояние переменной до инициализации называется uninitialized. При попытке прочитать uninitialized переменную движок выбрасывает ReferenceError. Это отличается от undefined — специального значения JavaScript для «объявлено, но не присвоено».

Внутри V8 и SpiderMonkey TDZ-переменные хранятся с маркером the_hole (специальное sentinel-значение, недоступное из JS-кода). Обращение к the_hole в специальных точках кода приводит к выбросу ReferenceError.

Практические правила

  1. Объявляй переменные в начале скоупа — так TDZ никогда не будет проблемой
  2. Не полагайся на typeof для проверки let/const — используй try/catch или другие паттерны
  3. Классы — не поднимаются: всегда объявляй класс перед использованием