Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке тимчасова зона мертвих (TDZ) у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Temporal Dead Zone (TDZ)** - це період між входом у блочну область видимості та рядком ініціалізації `let`/`const`. Звернення до змінної в цей час кидає `ReferenceError`. ```javascript console.log(x); // ReferenceError: Cannot access 'x' before initialization let x = 5; console.log(x); // 5 ``` **Головне:** `let`/`const` підіймаються, але залишаються неініціалізованими. `var` одразу отримує `undefined` при підйомі, тому TDZ у нього немає.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 ловить це в розробці, а не в продакшені.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.