NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Временная мёртвая зона (TDZ)

JavaScript (ES6 и новее)Общие возможности ES6По умолчанию

Основная идея

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

Ключевые аспекты

  • Hoisting существует — переменные let/const поднимаются, но не инициализируются
  • Безопасность кода — TDZ предотвращает использование переменных до их объявления
  • Применимость — работает только с let и const, не с var
  • Область действия — от начала блока до строки объявления
  • Typeof не спасает — даже typeof вызовет ReferenceError в TDZ

Плюсы TDZ

  • Помогает выявить ошибки на этапе выполнения
  • Делает код предсказуемым и читаемым
  • Предотвращает использование неинициализированных значений

Минусы

  • Может вызвать неожиданные ошибки при рефакторинге
  • Требует понимания механизма для отладки
  • Отличается от поведения var, что может запутать

Частые ошибки на собеседованиях

  • Путают hoisting с отсутствием hoisting (let/const тоже поднимаются, просто находятся в TDZ)
  • Думают, что typeof безопасен для переменных в TDZ
  • Не понимают, что TDZ определяется временем выполнения, а не позицией в коде

Введение и проблематика

До появления ES6 в JavaScript существовала только одна конструкция для объявления переменных — var. Эта конструкция имела особенность: переменные "поднимались" (hoisting) в начало функции и инициализировались значением undefined.

Code Example 1: Что выведет console.log? Почему не возникает ошибка?

js
console.log(x); // undefined (не ошибка!)
var x = 5;

Такое поведение часто приводило к трудноуловимым багам. С введением let и const в ES6 появился механизм Temporal Dead Zone (TDZ), который делает код более предсказуемым.

TDZ — это защитный механизм, который не позволяет использовать переменную до её объявления.


Базовая теория

Что такое Temporal Dead Zone?

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

graph LR A[Начало блока] --> B[TDZ] B --> C[let x = 5] C --> D[Переменная доступна] style B fill:#ff6b6b,color:#fff style D fill:#51cf66,color:#fff

Почему "временная"?

TDZ определяется временем выполнения, а не позицией в коде. Переменная находится в TDZ с момента входа в область видимости до выполнения строки с объявлением.

Code Example 2: Что произойдёт при выполнении каждого console.log? Почему поведение отличается?

js
{
  // TDZ начинается здесь для переменной x
 
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
 
  let x = 10; // TDZ заканчивается
 
  console.log(x); // 10
}

Практические примеры

Сравнение var, let и const

Code Example 3: Что выведет каждый пример? В чём разница поведения при обращении до объявления?

js
// var — hoisting с инициализацией undefined
console.log(name); // undefined
var name = 'Иван';
console.log(name); // 'Иван'
 
// Это эквивалентно:
var name;
console.log(name); // undefined
name = 'Иван';

TDZ в условных конструкциях

js
let condition = true;
 
if (condition) {
  // TDZ для message начинается здесь
 
  // console.log(message); // ReferenceError
 
  let message = 'Привет!';
  console.log(message); // 'Привет!'
}
 
// message здесь не существует (блочная область видимости)

TDZ в циклах

Code Example 4: Что выведут setTimeout-колбэки? Почему let ведёт себя иначе, чем var в цикле?

js
// Безопасное использование в for
for (let i = 0; i < 3; i++) {
  console.log(i); // 0, 1, 2
}
 
// Каждая итерация создаёт новую переменную
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Выведет: 0, 1, 2 (не 3, 3, 3 как с var)

TDZ и функции

Code Example 5: Что произойдёт при вызове example()? Что произойдёт при вызове sayHello()? Почему результаты различаются?

js
// TDZ работает и с функциями, использующими переменные
function example() {
  console.log(value); // ReferenceError
  let value = 42;
}
 
// Но function declaration поднимается полностью
sayHello(); // 'Привет!'
function sayHello() {
  console.log('Привет!');
}

Пограничные кейсы

⚠️

Осторожно: typeof не защищает от TDZ!

Code Example 6: Что выведет каждый console.log? Почему typeof ведёт себя по-разному?

js
// Для необъявленных переменных typeof безопасен
console.log(typeof undeclared); // 'undefined'
 
// Но для переменных в TDZ — ошибка!
console.log(typeof x); // ReferenceError
let x = 5;

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

Code Example 7: Что произойдёт при вызове greet()? Почему greetFixed() работает корректно?

js
// Параметры обрабатываются слева направо
function greet(name = defaultName, defaultName = 'Гость') {
  console.log(name);
}
 
greet(); // ReferenceError: Cannot access 'defaultName' before initialization
 
// Правильный порядок:
function greetFixed(defaultName = 'Гость', name = defaultName) {
  console.log(name);
}
 
greetFixed(); // 'Гость'
🚫

Антипаттерн: обращение к переменной в её же инициализаторе

Code Example 8: Что произойдёт при выполнении этого кода? Почему?

js
// ❌ Ошибка — x ещё в TDZ
let x = x + 1; // ReferenceError
 
// ✅ Правильно
let y = 1;
let x = y + 1;

Продвинутые аспекты

TDZ и классы

Классы также подчиняются правилам TDZ:

Code Example 9: Какой из двух примеров вызовет ошибку? Почему?

js
// ❌ Ошибка — класс в TDZ
const instance = new MyClass(); // ReferenceError
 
class MyClass {
  constructor() {
    this.name = 'Класс';
  }
}
 
// ✅ Правильно
class MyClass {
  constructor() {
    this.name = 'Класс';
  }
}
 
const instance = new MyClass();

TDZ и замыкания

Code Example 10: Что выведет каждый вызов funcs0, funcs1, funcs2? Почему?

js
let funcs = [];
 
for (let i = 0; i < 3; i++) {
  funcs.push(() => i);
}
 
// Каждое замыкание захватывает своё значение i
console.log(funcs[0]()); // 0
console.log(funcs[1]()); // 1
console.log(funcs[2]()); // 2

Плюсы и минусы

Аспектvarlet/const (с TDZ)
Предсказуемость❌ Можно использовать до объявления✅ Ошибка при раннем доступе
Отладка❌ Скрытые баги с undefined✅ Явные ошибки
HoistingПоднимается и инициализируетсяПоднимается, но в TDZ
Область видимостиФункцияБлок

Вопросы интервьюера

Q: Что такое TDZ и зачем она нужна?

TDZ — временной промежуток, когда переменная let/const существует, но недоступна. Нужна для предотвращения использования неинициализированных переменных.

Q: В чём разница между hoisting для var и let?

var поднимается и инициализируется undefined. let поднимается, но остаётся в TDZ до объявления — доступ вызывает ReferenceError.

Q: Почему typeof не работает с переменными в TDZ?

Переменная существует в области видимости (hoisting), поэтому движок пытается к ней обратиться, но она в TDZ — результат ReferenceError.

Q: Как TDZ влияет на параметры функций по умолчанию?

Параметры обрабатываются слева направо. Нельзя использовать параметр в значении по умолчанию до его объявления.


Источники

Code Example 1: Hoisting var

❓ Что выведет console.log? Почему не возникает ошибка?

js
console.log(x);
var x = 5;

Code Example 2: Temporal Dead Zone

❓ Что произойдёт при выполнении каждого console.log? Почему поведение отличается?

js
{
  console.log(x);
 
  let x = 10;
 
  console.log(x);
}

Code Example 3: var vs let vs const

❓ Что выведет каждый пример? В чём разница поведения при обращении до объявления?

var:

js
console.log(name);
var name = 'Иван';
console.log(name);

let:

js
console.log(name);
let name = 'Иван';
console.log(name);

const:

js
console.log(PI);
const PI = 3.14159;
console.log(PI);

Code Example 4: TDZ в циклах

❓ Что выведут setTimeout-колбэки? Почему let ведёт себя иначе, чем var в цикле?

js
for (let i = 0; i < 3; i++) {
  console.log(i);
}
 
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

Code Example 5: TDZ и функции

❓ Что произойдёт при вызове example()? Что произойдёт при вызове sayHello()? Почему результаты различаются?

js
function example() {
  console.log(value);
  let value = 42;
}
 
sayHello();
function sayHello() {
  console.log('Привет!');
}

Code Example 6: typeof и TDZ

❓ Что выведет каждый console.log? Почему typeof ведёт себя по-разному?

js
console.log(typeof undeclared);
 
console.log(typeof x);
let x = 5;

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

❓ Что произойдёт при вызове greet()? Почему greetFixed() работает корректно?

js
function greet(name = defaultName, defaultName = 'Гость') {
  console.log(name);
}
 
greet();
 
function greetFixed(defaultName = 'Гость', name = defaultName) {
  console.log(name);
}
 
greetFixed();

Code Example 8: Самоссылка в инициализаторе

❓ Что произойдёт при выполнении этого кода? Почему?

js
let x = x + 1;

Code Example 9: TDZ и классы

❓ Какой из двух примеров вызовет ошибку? Почему?

js
const instance1 = new MyClass();
 
class MyClass {
  constructor() {
    this.name = 'Класс';
  }
}
js
class MyClass {
  constructor() {
    this.name = 'Класс';
  }
}
 
const instance2 = new MyClass();

Code Example 10: TDZ и замыкания

❓ Что выведет каждый вызов funcs0, funcs1, funcs2? Почему?

js
let funcs = [];
 
for (let i = 0; i < 3; i++) {
  funcs.push(() => i);
}
 
console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());