Hoisting (поднятие) — это механизм JavaScript, при котором объявления переменных и функций "перемещаются" в начало своей области видимости на этапе компиляции. Это позволяет использовать переменные и функции до их фактического объявления в коде.
Ключевые аспекты
var — поднимается объявление, но не инициализация (значение undefined до присваивания)
let и const — поднимаются, но находятся в "временной мёртвой зоне" (TDZ) до объявления
Function Declaration — поднимается полностью, включая тело функции
Function Expression — поднимается только переменная, не функция
Область видимости — hoisting работает в пределах текущей области (функции или блока)
Плюсы
Позволяет вызывать function declaration до объявления
Упрощает взаимную рекурсию функций
Даёт гибкость в организации кода
Минусы
Может приводить к неочевидному поведению
Источник многих багов у начинающих разработчиков
Переменные var доступны до объявления со значением undefined
Частые ошибки на собеседованиях
Путают hoisting переменных и hoisting функций — они работают по-разному
Забывают про Temporal Dead Zone для let и const
Считают, что hoisting физически перемещает код — на самом деле это поведение интерпретатора
Не различают function declaration и function expression в контексте hoisting
Полагают, что var в цикле создаёт новую переменную на каждой итерации
Введение и проблематика
В большинстве языков программирования нельзя использовать переменную или функцию до её объявления — компилятор выдаст ошибку. JavaScript ведёт себя иначе благодаря механизму hoisting.
Hoisting (поднятие) — это поведение JavaScript, при котором объявления переменных и функций обрабатываются до выполнения кода. Это создаёт иллюзию, что объявления "поднимаются" в начало области видимости.
Hoisting не перемещает код физически. Это особенность работы интерпретатора JavaScript, который сначала сканирует код на наличие объявлений, а затем выполняет его.
Базовая теория
Две фазы выполнения кода
JavaScript-движок обрабатывает код в две фазы:
Фаза компиляции (создания) — движок сканирует код и регистрирует все объявления переменных и функций
Фаза выполнения — код выполняется построчно
❓
Code Example 1: Что выведет console.log в каждом случае? Почему первый вызов не вызывает ошибку?
js
console.log(message); // undefined (не ошибка!)var message ='Hello';console.log(message); // 'Hello'
Как это видит интерпретатор:
js
var message; // объявление поднятоconsole.log(message); // undefinedmessage ='Hello'; // присваивание на местеconsole.log(message); // 'Hello'
Hoisting переменных
Переменные var
Объявление var поднимается, но присваивание остаётся на месте.
js
console.log(x); // undefinedvar x =5;console.log(x); // 5
js
console.log(x);var x =5;console.log(x);
Переменные let и const
Объявления let и const также поднимаются, но находятся в Temporal Dead Zone (TDZ) до строки объявления.
js
// TDZ для myVar начинается здесьconsole.log(myVar); // ReferenceError: Cannot access 'myVar' before initializationlet myVar =10; // TDZ заканчивается здесь
🚫
Temporal Dead Zone (TDZ) — это промежуток от начала блока до строки объявления переменной, где обращение к переменной вызывает ReferenceError.
Сравнение var, let и const
❓
Code Example 2: Какая ошибка произойдёт в testLet() и testConst()? Почему в testVar() нет ошибки?
js
// var — hoisting с undefinedfunctiontestVar() {console.log(a); // undefinedvar a =1;}// let — hoisting с TDZfunctiontestLet() {console.log(b); // ReferenceErrorlet b =2;}// const — hoisting с TDZfunctiontestConst() {console.log(c); // ReferenceErrorconstc=3;}
Hoisting функций
Function Declaration
Поднимается полностью — и объявление, и тело функции.
js
// Можно вызвать до объявленияsayHello(); // 'Hello!'functionsayHello() {console.log('Hello!');}
Function Expression
Поднимается только переменная, не функция.
js
sayHi(); // TypeError: sayHi is not a functionvarsayHi=function() {console.log('Hi!');};
❓
Code Example 3: Какой код выполнится успешно, а какой вызовет ошибку? Почему?
js
// ✅ Работает — функция полностью поднятаgreet('World');functiongreet(name) {console.log('Hello, '+ name);}
Arrow Function
Ведёт себя как function expression — переменная поднимается, но не функция.
js
add(2,3); // TypeError: add is not a functionvaradd= (a, b) => a + b;
Практические примеры
Порядок hoisting
Функции поднимаются раньше переменных.
❓
Code Example 4: Что выведет typeof foo в каждом случае? Почему результаты разные?
functionfoo() { // 1. функция поднята первойreturn'baz';}var foo; // 2. переменная поднята (но не перезаписывает функцию)console.log(typeof foo); // 'function' — пока не было присваиванияfoo ='bar'; // 3. присваивание перезаписываетconsole.log(typeof foo); // 'string'
Hoisting в циклах
❓
Code Example 5: Что выведут эти два цикла? Почему результаты разные?
js
for (var i =0; i <3; i++) {setTimeout(function() {console.log(i); // 3, 3, 3 },100);}// var i поднимается в начало функции// к моменту выполнения setTimeout, i уже равно 3
Решение с let:
js
for (let i =0; i <3; i++) {setTimeout(function() {console.log(i); // 0, 1, 2 },100);}// let создаёт новую переменную на каждой итерации
Hoisting внутри функций
❓
Code Example 6: Что выведет функция printName()? Почему первый console.log выводит не 'Global'?
js
var name ='Global';functionprintName() {console.log(name); // undefined (не 'Global'!)var name ='Local';console.log(name); // 'Local'}printName();
⚠️
Локальный var name поднимается в начало функции и "затеняет" глобальную переменную до присваивания.
Hoisting и модульная изоляция (Node.js)
❓
Code Example 7: Почему переменная x из file1.ts недоступна в file2.ts? Чем поведение var в Node.js отличается от браузера?
В Node.js каждый файл — это отдельный модуль. Hoisting работает только в рамках текущего модуля:
ts
// file1.tsvar x ='hello';// file2.tsconsole.log(x); // ❌ ReferenceError — x недоступна
В отличие от браузерных скриптов, где var попадает в глобальный window, в Node.js переменные изолированы внутри модуля.
Пограничные кейсы
Hoisting в блоках (if, for)
js
// var игнорирует блочную область видимостиif (true) {var blockVar ='inside if';}console.log(blockVar); // 'inside if'// let и const — блочная область видимостиif (true) {let blockLet ='inside if';}console.log(blockLet); // ReferenceError
Несколько объявлений одной переменной
js
var x =1;var x =2; // ✅ Допустимо для varconsole.log(x); // 2let y =1;let y =2; // ❌ SyntaxError: Identifier 'y' has already been declared
Функции внутри блоков
js
// Поведение зависит от режима!if (true) {functiontest() {return'inside'; }}// В strict mode: test недоступна снаружи// В non-strict mode: поведение может отличаться между браузерами
🚫
Избегайте объявления функций внутри блоков (if, for). Поведение нестандартизировано и зависит от движка.
Плюсы и минусы
Аспект
var
let/const
Hoisting
Да, со значением undefined
Да, но TDZ
Область видимости
Функция
Блок
Повторное объявление
✅ Разрешено
❌ Ошибка
Предсказуемость
❌ Неочевидное поведение
✅ Явные ошибки
Использование до объявления
undefined
ReferenceError
Вопросы интервьюера
Q: Что такое hoisting?
Механизм JavaScript, при котором объявления переменных и функций обрабатываются до выполнения кода, создавая эффект "поднятия" в начало области видимости.
Q: Что такое Temporal Dead Zone?
TDZ — промежуток от начала блока до строки объявления let/const, где обращение к переменной вызывает ReferenceError.
Q: Чем hoisting var отличается от let?
var поднимается со значением undefined и имеет функциональную область видимости. let поднимается, но находится в TDZ и имеет блочную область видимости.
Q: Почему function declaration поднимается полностью, а function expression — нет?
Function declaration — это специальная синтаксическая конструкция. Function expression — это выражение, присваивающее функцию переменной, поэтому работают правила hoisting переменных.
Q: Как избежать проблем с hoisting?
Использовать let/const вместо var, объявлять переменные в начале области видимости, использовать strict mode.