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
uninitialized. При попытке прочитать uninitialized переменную движок выбрасывает ReferenceError. Это отличается от undefined — специального значения JavaScript для «объявлено, но не присвоено».Внутри V8 и SpiderMonkey TDZ-переменные хранятся с маркером the_hole (специальное sentinel-значение, недоступное из JS-кода). Обращение к the_hole в специальных точках кода приводит к выбросу ReferenceError.
Практические правила
- Объявляй переменные в начале скоупа — так TDZ никогда не будет проблемой
- Не полагайся на
typeofдля проверкиlet/const— используйtry/catchили другие паттерны - Классы — не поднимаются: всегда объявляй класс перед использованием