Skip to main content

Що таке тимчасова зона мертвих (TDZ) у JavaScript

Temporal Dead Zone (TDZ) - це період від початку блочної області видимості до рядка, де ініціалізується let або const. Звернення до змінної в цей час кидає ReferenceError.

Теорія

TL;DR

  • TDZ - як полиця з табличкою "зарезервовано": слот існує в пам'яті, але читати його не можна.
  • var одразу ініціалізується до undefined при підйомі (hoisting). let/const залишаються неініціалізованими до рядка присвоєння.
  • Звернення до let/const до цього рядка дає ReferenceError, не undefined.
  • У сучасному коді використовуй let/const. TDZ перетворює тихі баги на явні помилки.

Швидкий приклад

javascript
console.log(a); // ReferenceError: Cannot access 'a' before initialization let a = 5; console.log(a); // 5 console.log(b); // undefined - немає TDZ для var var b = 5;

Обидві змінні підняті (hoisted). Але var b одразу отримує undefined, тоді як let a залишається неініціалізованою до виконання рядка 2.

Ключова різниця

var підіймається і автоматично ініціалізується до undefined, тому читання до оголошення повертає значення без помилки. let і const підіймають лише байндинг у лексичне середовище (lexical environment), залишаючи його неініціалізованим. Будь-яке читання до рядка присвоєння кидає ReferenceError. TDZ закінчується в момент виконання присвоєння.

Коли що використовувати

  • Блочна змінна, що змінюється → let.
  • Значення, яке не повинно змінюватись → const (TDZ плюс заборона перепризначення).
  • Старий for-цикл у pre-ES6 бібліотеці → var, і більше жодних причин.
  • React-хуки, Express-обробники, імпорти модулів → const за замовчуванням.

Таблиця порівняння

ЗміннаПідіймаєтьсяІніціалізується при підйоміTDZДоступ до ініціалізації
varТакundefinedНіПовертає undefined
letТакНіТакReferenceError
constТакНіТакReferenceError

Класи, оголошені через class, мають те саме правило що й let. Звернення до класу до рядка його оголошення теж кидає.

Як це працює в движку

V8 виконує два проходи. Перший підіймає var-оголошення і ставить undefined. Для let/const створює неініціалізований байндинг у записі лексичного середовища (lexical environment record). Другий прохід виконує код рядок за рядком. Читання неініціалізованого байндингу кидає ReferenceError. Ось і все, що таке TDZ на рівні движка.

Типові помилки

Думати, що let взагалі не підіймається:

javascript
let x; console.log(x); // undefined - оголошено, але ще не присвоєно значення x = 10; console.log(x); // 10

let підіймається. Просто залишається неініціалізованою. Тому рання помилка звучить як "cannot access before initialization", а не "x is not defined".

Деструктурування параметрів без значень за замовчуванням:

javascript
function fn({ a }) { console.log(a); } fn(); // ReferenceError - деструктурований байндинг має власний TDZ fn({}); // undefined - нормально

Рішення: function fn({ a = 1 }).

Плутати ReferenceError і TypeError:

javascript
const x = 1; x = 2; // TypeError, не ReferenceError

ReferenceError - це TDZ, до ініціалізації. TypeError - це спроба перепризначити const після завершення TDZ. Різні помилки, різні причини.

Більшість TDZ-багів, які я бачив на code review, з'являються після рефакторингу var на let без перевірки порядку читання в тому ж блоці.

Де зустрічається у реальному коді

  • React: const [state, setState] = useState(null) - TDZ не дасть прочитати стейт до рядка з хуком.
  • Express: const id = req.params.id всередині обробника маршруту - ловить звернення до параметрів до їх парсингу.
  • Node.js-модулі: const fs = require('fs') на верхньому рівні - strict mode тримає TDZ по всьому файлу.
  • Redux: const action = { type: 'FETCH_USER' } - не дає undefined потрапити в reducer.

Питання на співбесіді

Q: Що виведе console.log(x); let x = 5;?
A: ReferenceError: Cannot access 'x' before initialization. Змінна підіймається, але залишається в TDZ до рядка присвоєння.

Q: Чи є TDZ у const?
A: Так, ті самі правила що й у let. TDZ закінчується на рядку присвоєння. Після цього спроба перепризначення кидає TypeError, не ReferenceError.

Q: Навіщо взагалі потрібен TDZ?
A: Щоб зробити ранній доступ до змінної явною помилкою. З var такий доступ повертає undefined без жодного попередження - і це джерело багів, які важко відстежити.

Q: Як Babel імітує TDZ при транспіляції в ES5?
A: Babel використовує перевірку через void 0 як сентинел для неініціалізованого стану. Наближення до специфікації, але в крайніх випадках поведінка може відрізнятись від нативного движка.

Приклади

Базова демонстрація TDZ

javascript
// Обидві змінні підняті з початку блоку. // var: одразу ініціалізується до undefined. // let: залишається неініціалізованою до рядка оголошення. console.log(name); // undefined (var - немає TDZ) var name = 'Alice'; console.log(age); // ReferenceError: Cannot access 'age' before initialization let age = 30;

name повертає undefined, бо var ініціалізується при підйомі. age кидає помилку, бо let залишає байндинг неініціалізованим до рядка 9.

TDZ в обробнику запиту

javascript
// Безпечний патерн - доступ після оголошення function handleLogin(userId) { if (!userId) throw new Error('Відсутній ID користувача'); const token = generateToken(userId); // TDZ вже закінчився до цього рядка return token; } // Небезпечний патерн - порушення TDZ function badHandler(userId) { console.log(token); // ReferenceError - token в TDZ const token = generateToken(userId); return token; }

У handleLogin token читається тільки після рядка оголошення. У badHandler читання token до рядка з const одразу кидає помилку. TDZ ловить це в розробці, а не в продакшені.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?