Перейти к основному содержимому

[Medium] 📄 Hoisting

1. Что такое Hoisting?

Выполнение JavaScript можно рассматривать как две фазы: создание и выполнение.

var name = 'Pitt';
console.log(name); // выводит Pitt

При hoisting движок концептуально сначала обрабатывает объявление, а затем присваивание:

// создание
var name;

// выполнение
name = 'Pitt';
console.log(name);

Функции ведут себя иначе, чем переменные. Объявление функции привязывается во время фазы создания:

getName();

function getName() {
console.log('string'); // выводит string
}

Это работает, потому что объявление функции поднимается до вызова во время выполнения:

// создание
function getName() {
console.log('string');
}

// выполнение
getName();

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

В фазе создания объявления функций имеют более высокий приоритет, чем объявления переменных.

Правильно (Correct)

name = 'Yumy';
console.log(name); // выводит Yumy
var name;

// --- Эквивалентно ---

// создание
var name;

// выполнение
name = 'Yumy';
console.log(name); // выводит Yumy

Неправильно (Wrong)

console.log(name); // выводит undefined
var name = 'Jane';

// --- Эквивалентно ---

// создание
var name;

// выполнение
console.log(name); // выводит undefined, потому что присваивание ещё не произошло
name = 'Pitt';

2. Что выведет name?

whoseName();

function whoseName() {
if (name) {
name = 'Nini';
}
}

var name = 'Pitt';
console.log(name);

Ответ

// создание
function whoseName() {
if (name) {
name = 'Nini';
}
}
var name;

// выполнение
whoseName();
name = 'Pitt';
console.log(name); // выводит Pitt

Внутри whoseName() значение name начинается как undefined, поэтому ветка if (name) не выполняется. После этого name получает значение 'Pitt', поэтому финальный вывод всё равно Pitt.


3. Объявление функции vs объявление переменной: приоритет Hoisting

Вопрос: функция и переменная с одинаковым именем

Предскажите вывод этого кода:

console.log(foo);
var foo = '1';
function foo() {}

Распространённые неправильные ответы

Многие думают, что выведется:

  • undefined (предполагая, что var поднимается первым)
  • '1' (предполагая, что присваивание уже произошло)
  • Ошибка (предполагая конфликт одинаковых имён)

Фактический вывод

[Function: foo]

Почему?

Этот вопрос проверяет правило приоритета hoisting:

Приоритет hoisting: объявление функции > объявление переменной

// Исходный код
console.log(foo);
var foo = '1';
function foo() {}

// Эквивалент после hoisting
// Фаза 1: создание (hoisting)
function foo() {} // 1. объявление функции поднимается первым
var foo; // 2. объявление переменной поднимается (не перезаписывает функцию)

// Фаза 2: выполнение
console.log(foo); // foo здесь — функция
foo = '1'; // 3. присваивание перезаписывает функцию

Ключевые концепции (Key Concepts)

1. Объявления функций полностью поднимаются

console.log(myFunc); // [Function: myFunc]

function myFunc() {
return 'Hello';
}

2. var поднимает только объявление, не присваивание

console.log(myVar); // undefined

var myVar = 'Hello';

3. Когда объявления функции и переменной имеют одинаковое имя

// Порядок hoisting
function foo() {} // функция поднимается и инициализируется первой
var foo; // объявление поднимается, но не перезаписывает функцию

// Поэтому foo по-прежнему функция
console.log(foo); // [Function: foo]

Полный порядок выполнения (Full Execution Flow)

// Исходный код
console.log(foo); // ?
var foo = '1';
function foo() {}
console.log(foo); // ?

// ======== Эквивалент ========

// Фаза создания (hoisting)
function foo() {} // 1️⃣ объявление функции полностью поднимается
var foo; // 2️⃣ объявление переменной поднимается, но не заменяет функцию

// Фаза выполнения
console.log(foo); // [Function: foo]
foo = '1'; // 3️⃣ присваивание теперь перезаписывает функцию
console.log(foo); // '1'

Расширенные вопросы (Extended Questions)

Вопрос A: меняет ли порядок результат?

console.log(foo); // ?
function foo() {}
var foo = '1';
console.log(foo); // ?

Ответ:

[Function: foo] // первый вывод
'1' // второй вывод

Причина: порядок в исходном коде не меняет приоритет hoisting. Объявление функции всё равно побеждает объявление переменной в фазе создания.

Вопрос B: несколько функций с одинаковым именем

console.log(foo); // ?

function foo() {
return 1;
}

var foo = '1';

function foo() {
return 2;
}

console.log(foo); // ?

Ответ:

[Function: foo] // первый вывод (позднее объявление функции переопределяет раннее)
'1' // второй вывод (присваивание перезаписывает функцию)

Причина:

// После hoisting
function foo() {
return 1;
} // первая функция

function foo() {
return 2;
} // вторая функция переопределяет первую

var foo; // только объявление (не перезаписывает функцию)

console.log(foo); // функция, возвращающая 2
foo = '1'; // присваивание перезаписывает функцию
console.log(foo); // '1'

Вопрос C: функциональное выражение vs объявление функции

console.log(foo); // ?
console.log(bar); // ?

var foo = function () {
return 1;
};

function bar() {
return 2;
}

Ответ:

undefined // foo — это undefined
[Function: bar] // bar — это функция

Причина:

// После hoisting
var foo; // объявление переменной поднимается (тело функции — нет)
function bar() {
return 2;
} // объявление функции полностью поднимается

console.log(foo); // undefined
console.log(bar); // function

foo = function () {
return 1;
}; // присваивание во время выполнения

Ключевое различие:

  • Объявление функции: function foo() {} -> полностью поднимается (включая тело)
  • Функциональное выражение: var foo = function() {} -> поднимается только имя переменной

let/const избегают этого паттерна

// ❌ `var` имеет подводные камни с hoisting
console.log(foo); // undefined
var foo = '1';

// ✅ let/const находятся в TDZ до инициализации
console.log(bar); // ReferenceError
let bar = '1';

// ✅ использование одного имени с функцией и let/const вызывает ошибку
function baz() {}
let baz = '1'; // SyntaxError: Identifier 'baz' has already been declared

Сводка приоритетов Hoisting (Hoisting Priority Summary)

Приоритет hoisting (высокий -> низкий):

1. Объявление функции (Function Declaration)
- function foo() {} ✅ полностью поднимается
- наивысший приоритет

2. Объявление переменной (Variable Declaration)
- var foo ⚠️ только объявление (без присваивания)
- не перезаписывает существующее объявление функции

3. Присваивание переменной (Variable Assignment)
- foo = '1' ✅ может перезаписать функцию
- происходит только в фазе выполнения

4. Функциональное выражение (Function Expression)
- var foo = function() {} ⚠️ рассматривается как присваивание
- поднимается только имя переменной

Фокус на собеседовании (Interview Focus)

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

  1. Объясните hoisting как две фазы: создание и выполнение.
  2. Подчеркните приоритет: объявление функции > объявление переменной.
  3. Перепишите код в поднятую форму для обоснования.
  4. Упомяните лучшие практики: предпочитайте let/const и избегайте путаных паттернов с var.

Пример ответа на собеседовании:

Этот вопрос о приоритете hoisting. В JavaScript объявления функций поднимаются перед объявлениями переменных.

Движок проходит две фазы:

  1. Фаза создания: function foo() {} поднимается первым, затем var foo поднимается без перезаписи функции.
  2. Фаза выполнения: console.log(foo) выводит функцию, и только позже foo = '1' перезаписывает её.

На практике используйте let/const вместо var, чтобы избежать этой путаницы.