Skip to content
Иван Ткаченко Блог
EN
Хойстинг и зона смерти в JavaScript — Иван Ткаченко ← К статьям
JavaScript

Хойстинг и зона смерти в JavaScript

Продолжаю серию про базовые вопросы для подготовки к собесам. После event loop и замыканий следующая тема — хойстинг. Спрашивают реже, но ответ «переменные поднимаются» слишком слабый. Разберём, как оно работает под капотом.


Что такое хойстинг

JavaScript выполняет код в два этапа. Сначала проходит по всему коду в файле и регистрирует объявления: переменные, функции, классы. Потом выполняет код строка за строкой.

Хойстинг — следствие этой двухэтапной работы движка. Это поведение, при котором объявления оказываются в памяти раньше, чем выполнение до них доходит.

Вроде бы всё просто, но есть нюанс: не все объявления попадают в память одинаково.


function declaration

Объявление функции записывается в память целиком, вместе с телом. Функцию можно вызвать до её объявления в коде.

greet("Вася"); // 'Привет, Вася' — работает

function greet(name) {
  console.log(`Привет, ${name}`);
}

В фазе создания движок нашёл function greet и сразу записал её в память. К моменту вызова greet('Вася') функция уже была доступна, хотя в коде объявлена ниже.

Та же механика работает для асинхронных функций async function и генераторов function*: если объявление начинается с ключевого слова function, оно хойстится целиком.


var

В случае с var немного другое поведение: в память попадает только объявление переменной со значением undefined. Присвоение значения происходит позже, в момент выполнения кода.

console.log(name); // undefined — не ошибка
var name = "Вася";
console.log(name); // 'Вася'

В фазе создания движок увидел var name и зарезервировал место в памяти со значением undefined. Присвоение 'Вася' происходит, когда выполнение доходит до этой строки.


let и const: TDZ

let и const тоже регистрируются в фазе создания, но не инициализируются — у них нет даже значения undefined. Они существуют в памяти, но обратиться к ним нельзя — любая попытка до строки с объявлением бросит ReferenceError.

Этот промежуток называется Temporal Dead Zone, TDZ.

console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;
console.log(typeof age); // ReferenceError — даже typeof не спасает внутри TDZ
let age = 25;

В отличие от var, который молча возвращает undefined, обращение к let/const до объявления бросит ReferenceError — поведение более строгое и предсказуемое, меньше шансов упустить баг.


function expression и стрелочные функции

Если функция присвоена переменной, хойстинг работает по правилам этой переменной, а не по правилам функции.

greet(); // TypeError: greet is not a function

var greet = function () {
  console.log("Привет");
};

В фазе создания в память попало только var greet со значением undefined. До строки с присвоением greet это просто undefined, а не функция. Вызов undefined() даёт TypeError.

greet(); // ReferenceError: Cannot access 'greet' before initialization

const greet = () => {
  console.log("Привет");
};

В этом примере стрелочная функция объявлена через const, она попадает в TDZ — попытка вызвать её до объявления бросит ReferenceError.


class

Объявление класса движок обрабатывает как let/const: регистрирует в фазе создания, но не инициализирует — класс тоже попадает в TDZ.

const user = new User("Вася"); // ReferenceError

class User {
  constructor(name) {
    this.name = name;
  }
}

В отличие от function declaration, использовать класс до объявления нельзя.


Классический вопрос с собеса

Что выведет этот код?

console.log(typeof foo);
console.log(typeof bar);

var foo = 1;
function bar() {}

Без знания того, как работает хойстинг, ожидания могут быть разными: ошибка или undefined для обоих. На самом деле:

  • typeof foo'undefined': var foo зарегистрировался в фазе создания со значением undefined, typeof undefined возвращает строку 'undefined'
  • typeof bar'function': function bar записалась в память как function declaration — вместе с телом

А теперь усложнённый вариант:

var foo = 1;

function foo() {
  return 2;
}

console.log(typeof foo); // 'number'

В фазе создания function foo записывается в память первой. Затем выполнение доходит до var foo = 1 и перезаписывает значение числом. Объявление var foo игнорируется — имя уже зарезервировано функцией, но присвоение выполняется.


Почему это спрашивают на собеседовании

Хойстинг — это не самостоятельная фича языка, а скорее побочный эффект того, как движок читает код.

Несколько ситуаций, где незнание хойстинга приводит к проблемам:

  • Использование var до объявления. Переменная уже существует в памяти со значением undefined, хотя присвоение ещё не случилось. Код не падает с ошибкой, а молча работает не так, как ожидалось.
  • Временная мёртвая зона с let и const. Переменные уже зарегистрированы, но обратиться к ним нельзя — до строки с объявлением бросит ReferenceError. Выглядит неожиданно, если не знать про TDZ.
  • Разница между function declaration и function expression. Первая хойстится целиком и доступна до объявления. Вторая — нет. Выглядят похоже, ведут себя по-разному.
  • Смешивание function declaration и var с одним именем. Порядок выполнения не совпадает с порядком в коде, и результат зависит от того, что именно и когда попало в память.

На собеседовании эта тема позволяет понять, знаете ли вы, как движок выполняет код — и как не выстрелить себе в ногу из-за его особенностей.