Всплытие (hoisting) и TDZ

Всплытие (hoisting) и TDZ

Технический фундамент инициализации окружения

Перед исполнением кода движок готовит окружение:

  • регистрирует объявления в области видимости;
  • по-разному инициализирует var, let, const, function;
  • создает TDZ для блочных объявлений до строки инициализации;
  • из-за этого "обращение до объявления" может давать разные результаты.

Понимание этого этапа убирает ощущение "странной магии" и делает ошибки предсказуемыми.

Почему hoisting и TDZ часто ломают ожидания

Ты пишешь код сверху вниз и ожидаешь, что он именно так и выполнится. Но JavaScript сначала проходит фазу подготовки области видимости, и часть объявлений становится известной заранее. Из-за этого появляются сюрпризы: где-то undefined, где-то ReferenceError.

Ключевой момент: hoisting это не "магическое перемещение строк", а особенность этапа инициализации окружения выполнения.

Проверь себя: почему одинаковое обращение "до объявления" работает по-разному для var и let?

var и hoisting

console.log(count); // undefined
var count = 3;

Для var объявление известно заранее, но инициализация значением происходит на строке присваивания. Поэтому до нее получаешь undefined, а не значение.

Здесь часто путаются: "почему не ошибка?" - потому что имя уже зарегистрировано в области видимости.

let/const и TDZ

console.log(score); // ReferenceError
let score = 10;

Для let и const действует TDZ (Temporal Dead Zone) - временная зона, где переменная уже "существует" в scope, но к ней нельзя обращаться до строки инициализации.

const дополнительно требует инициализации сразу.

const limit = 70; // ок
// const limit; // SyntaxError

Смотри, что важно: TDZ защищает от использования переменной до корректной подготовки значения.

Еще один частый сюрприз: TDZ может сработать из-за shadowing внутри блока — даже если снаружи переменная с таким именем есть.

const mode = 'prod';

if (true) {
  // console.log(mode); // ReferenceError из-за TDZ локального mode
  const mode = 'test';
}

Hoisting функций

Function Declaration всплывает целиком, поэтому ее можно вызвать до объявления.

sayHello(); // works

function sayHello() {
  console.log('Hello');
}

Но Function Expression в переменной подчиняется правилам переменной.

// greet(); // ReferenceError/TypeError в зависимости от объявления
const greet = function () {
  console.log('Hi');
};

Смотри, что важно: если greet объявлен через var, то до присваивания он будет undefined, и вызов greet() даст TypeError. А если через let/const, получишь ReferenceError из TDZ.

Проверь себя: почему declaration и expression ведут себя по-разному при вызове "до объявления"?

Мини-сценарий: инициализация состояния

Представь модуль страницы:

  • сначала читается конфиг;
  • потом вычисляется режим;
  • затем рендерится экран.

Если в этом потоке обратиться к let-переменной до инициализации, экран упадет до рендера. Именно поэтому порядок инициализации критичен.

const mode = getMode(config);
const config = { isPreview: true }; // TDZ ошибка, если так написать

Важно: здесь упадет на чтении config, поэтому getMode(...) даже не успеет вызваться.

Правильный порядок:

const config = { isPreview: true };
const mode = getMode(config);

Частые ошибки новичков

  • Смешивать var и let в одном стиле кода.
  • Полагаться на "работает из-за hoisting" вместо явного порядка.
  • Вызывать function expression до объявления.
  • Игнорировать TDZ-ошибки как "странность языка".

Анти-провал: объявляй переменные до использования и не строй логику на побочных эффектах hoisting.

Что будет, если изменить входные данные

Если заменить var на let в старом коде с обращением до объявления, ты получишь ReferenceError. Это хорошо: ошибка становится явной и помогает исправить порядок инициализации, а не прятать проблему в undefined.

Проверь себя: почему явная ошибка в этом случае полезнее "тихого" undefined?

Краткий итог

  • hoisting связан с фазой подготовки окружения выполнения.
  • var доступен до присваивания как undefined.
  • let/const имеют TDZ и не дают обращаться к переменной до инициализации.
  • Function Declaration всплывает целиком, Function Expression - нет.
  • Лучший практический подход: явный порядок объявлений и минимум зависимости от hoisting-поведения.