NodeJS Back-end Инженер

NodeJS Back-end Инженер

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

Hoisting (поднятие)

Язык JavaScript (ES5)ОбщееОсновное

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

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-движок обрабатывает код в две фазы:

  1. Фаза компиляции (создания) — движок сканирует код и регистрирует все объявления переменных и функций
  2. Фаза выполнения — код выполняется построчно

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

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

Как это видит интерпретатор:

js
var message;          // объявление поднято
console.log(message); // undefined
message = 'Hello';    // присваивание на месте
console.log(message); // 'Hello'

Hoisting переменных

Переменные var

Объявление var поднимается, но присваивание остаётся на месте.

js
console.log(x); // undefined
var 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 initialization
let myVar = 10;     // TDZ заканчивается здесь
🚫

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

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

Code Example 2: Какая ошибка произойдёт в testLet() и testConst()? Почему в testVar() нет ошибки?

js
// var — hoisting с undefined
function testVar() {
  console.log(a); // undefined
  var a = 1;
}
 
// let — hoisting с TDZ
function testLet() {
  console.log(b); // ReferenceError
  let b = 2;
}
 
// const — hoisting с TDZ
function testConst() {
  console.log(c); // ReferenceError
  const c = 3;
}

Hoisting функций

Function Declaration

Поднимается полностью — и объявление, и тело функции.

js
// Можно вызвать до объявления
sayHello(); // 'Hello!'
 
function sayHello() {
  console.log('Hello!');
}

Function Expression

Поднимается только переменная, не функция.

js
sayHi(); // TypeError: sayHi is not a function
 
var sayHi = function() {
  console.log('Hi!');
};

Code Example 3: Какой код выполнится успешно, а какой вызовет ошибку? Почему?

js
// ✅ Работает — функция полностью поднята
greet('World');
 
function greet(name) {
  console.log('Hello, ' + name);
}

Arrow Function

Ведёт себя как function expression — переменная поднимается, но не функция.

js
add(2, 3); // TypeError: add is not a function
 
var add = (a, b) => a + b;

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

Порядок hoisting

Функции поднимаются раньше переменных.

Code Example 4: Что выведет typeof foo в каждом случае? Почему результаты разные?

js
console.log(typeof foo); // 'function'
 
var foo = 'bar';
 
function foo() {
  return 'baz';
}
 
console.log(typeof foo); // 'string'

Порядок интерпретации:

js
function foo() {     // 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';
 
function printName() {
  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.ts
var x = 'hello';
 
// file2.ts
console.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; // ✅ Допустимо для var
console.log(x); // 2
 
let y = 1;
let y = 2; // ❌ SyntaxError: Identifier 'y' has already been declared

Функции внутри блоков

js
// Поведение зависит от режима!
if (true) {
  function test() {
    return 'inside';
  }
}
 
// В strict mode: test недоступна снаружи
// В non-strict mode: поведение может отличаться между браузерами
🚫

Избегайте объявления функций внутри блоков (if, for). Поведение нестандартизировано и зависит от движка.


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

Аспектvarlet/const
HoistingДа, со значением undefinedДа, но TDZ
Область видимостиФункцияБлок
Повторное объявление✅ Разрешено❌ Ошибка
Предсказуемость❌ Неочевидное поведение✅ Явные ошибки
Использование до объявленияundefinedReferenceError

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

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.


Источники

Code Example 1: Basic hoisting

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

js
console.log(message);
var message = 'Hello';
console.log(message);

Code Example 2: var vs let/const

❓ Какая ошибка произойдёт в testLet() и testConst()? Почему в testVar() нет ошибки?

js
function testVar() {
  console.log(a);
  var a = 1;
}
 
function testLet() {
  console.log(b);
  let b = 2;
}
 
function testConst() {
  console.log(c);
  const c = 3;
}

Code Example 3: Function declaration vs expression

❓ Какой код выполнится успешно, а какой вызовет ошибку? Почему?

Version A:

js
greet('World');
 
function greet(name) {
  console.log('Hello, ' + name);
}

Version B:

js
greet('World');
 
var greet = function(name) {
  console.log('Hello, ' + name);
};

Code Example 4: Order of hoisting

❓ Что выведет typeof foo в каждом случае? Почему результаты разные?

js
console.log(typeof foo);
 
var foo = 'bar';
 
function foo() {
  return 'baz';
}
 
console.log(typeof foo);

Code Example 5: Hoisting in loops

❓ Что выведут эти два цикла? Почему результаты разные?

Version A:

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

Version B:

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

Code Example 6: Hoisting inside functions

❓ Что выведет функция printName()? Почему первый console.log выводит не 'Global'?

js
var name = 'Global';
 
function printName() {
  console.log(name);
  var name = 'Local';
  console.log(name);
}
 
printName();

Code Example 7: Модульная изоляция в Node.js

❓ Почему переменная x из file1.ts недоступна в file2.ts? Чем поведение var в Node.js отличается от браузера?

ts
// file1.ts
var x = 'hello';
 
// file2.ts
console.log(x);