← Назад к вопросам

Как работает Hoisting относительно let?

1.6 Junior🔥 281 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает Hoisting относительно let?

Hoisting (поднятие) — это одна из самых запутанных особенностей JavaScript. Большинство разработчиков думают, что `let` не поднимается (hoisted), но это неправда. Разница в том, КАК это происходит.

Что такое Hoisting?

Hoisting — это поведение JavaScript, при котором объявления переменных и функций "поднимаются" в начало своей scope (область видимости).

Это происходит потому что JavaScript имеет две фазы выполнения:

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

var vs let/const — разные способы hoisting

var — инициализируется как undefined:

console.log(x); // undefined (не ошибка!)
var x = 5;
console.log(x); // 5

// JavaScript интерпретирует это как:
// var x;              <- Hoisting
// console.log(x);     <- undefined
// x = 5;
// console.log(x);     <- 5

let/const — остаются в TDZ (Temporal Dead Zone):

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;

// JavaScript видит:
// <- TDZ начинается
// console.log(y);  <- Попытка доступа в TDZ — ошибка!
// y = 5;           <- TDZ заканчивается

Temporal Dead Zone (TDZ)

TDZ — это зона в коде от начала scope'а до строки, где переменная объявлена.

В этой зоне переменная существует в памяти, но в состоянии "неинициализировано" (не undefined, а действительно неинициализировано).

{
  // === НАЧАЛО TDZ для z ===
  console.log(z); // ReferenceError
  
  let z = 10;
  // === КОНЕЦ TDZ для z ===
  
  console.log(z); // 10
}

// Попытка доступа в TDZ дает специальную ошибку,
// отличную от обычного ReferenceError для undefined переменной

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

Пример 1: let в глобальной scope

console.log(typeof x); // "undefined" — var поднялась
console.log(typeof y); // ReferenceError — let в TDZ

var x = 1;
let y = 2;

Пример 2: let в блоке кода

if (true) {
  console.log(name); // ReferenceError: Cannot access 'name' before initialization
  let name = 'John';
}

// name существует, но недоступна до инициализации

Пример 3: Функции и параметры

function test(x = y) {  // x инициализируется как y
  let y = 5;
  return x;
}

test(); // ReferenceError: Cannot access 'y' before initialization

// Почему? Параметры вычисляются в другой scope,
// где y еще в TDZ

Пример 4: Цикл for с let

for (let i = 0; i < 3; i++) {
  // 'i' имеет собственную binding в каждой итерации
  setTimeout(() => console.log(i), 100);
}

// Вывод: 0, 1, 2 (не 3, 3, 3)

// Эквивалентно:
{
  let i = 0;
  // ...
}
{
  let i = 1;
  // ...
}
{
  let i = 2;
  // ...
}

Разница между var, let и const

// var — функциональная scope, hoisting с undefined
function varExample() {
  console.log(x); // undefined
  var x = 1;
  if (true) {
    var x = 2; // перезаписывает внешний x!
  }
  console.log(x); // 2
}

// let — блоковая scope, hoisting в TDZ
function letExample() {
  // console.log(y); // ReferenceError
  let y = 1;
  if (true) {
    let y = 2; // новая переменная в этом блоке
  }
  console.log(y); // 1
}

// const — как let, но не переназначяемая
function constExample() {
  // console.log(z); // ReferenceError
  const z = 1;
  if (true) {
    const z = 2; // новая переменная
  }
  console.log(z); // 1
  // z = 5; // TypeError: Assignment to constant variable
}

Hoisting функций

Function declarations поднимаются полностью:

console.log(typeof greet); // "function"
console.log(greet('John')); // "Hello, John!"

function greet(name) {
  return `Hello, ${name}!`;
}

// Эквивалентно:
// function greet(name) { ... }  <- Весь код поднялся
// console.log(typeof greet);
// console.log(greet('John'));

Function expressions не поднимаются:

console.log(typeof sayBye); // undefined (var поднялась, но не значение)
// console.log(sayBye()); // TypeError: sayBye is not a function

var sayBye = function(name) {
  return `Goodbye, ${name}!`;
};

// С let — ошибка в TDZ:
// console.log(sayBye); // ReferenceError
let sayBye2 = function(name) {
  return `Goodbye, ${name}!`;
};

Класс примеры (ES6+)

Классы ведут себя как let:

console.log(typeof MyClass); // ReferenceError: Cannot access 'MyClass' before initialization

class MyClass {
  constructor() {}
}

// Классы в TDZ, как let

Практический совет: как избежать проблем

Правило 1: Объявляй переменные в начале блока

// Плохо: переменная объявлена внизу
function process() {
  console.log(data); // ReferenceError
  
  // ... много кода ...
  
  let data = [];
}

// Хорошо: переменная в начале
function process() {
  let data = [];
  
  console.log(data); // []
  
  // ... код ...
}

Правило 2: Используй let вместо var

// Плохо: var имеет функциональную scope
function badExample() {
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
  }
}
// Вывод: 3, 3, 3

// Хорошо: let имеет блоковую scope
function goodExample() {
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
  }
}
// Вывод: 0, 1, 2

Правило 3: Помни о TDZ в функциях

const getValue = (x = defaultValue) => x;
const defaultValue = 5;

getValue(); // ReferenceError: Cannot access 'defaultValue' before initialization

// Параметры вычисляются в своей scope до инициализации defaultValue

Визуализация Hoisting

// Исходный код:
function example() {
  console.log(a);      // ?
  console.log(b);      // ?
  
  var a = 1;
  let b = 2;
}

// После hoisting (концептуально):
function example() {
  var a;               // undefined
  let b;               // TDZ
  
  console.log(a);      // undefined
  console.log(b);      // ReferenceError!
  
  a = 1;
  b = 2;
}

Сложный пример: Closure + Hoisting

function outer() {
  console.log(x); // ReferenceError
  
  var f = function() {
    console.log(x); // undefined (var поднялась в outer)
  };
  
  var x = 5;
  return f;
}

const fn = outer(); // ReferenceError на первой строке

// Но если исправить:
function outer2() {
  var x; // Hoisted
  
  console.log(x); // undefined
  
  var f = function() {
    console.log(x); // undefined, потом 5
  };
  
  x = 5;
  f(); // 5
  return f;
}

Заключение

Ключевые моменты:

  1. let И const поднимаются, но в Temporal Dead Zone
  2. TDZ — зона от начала scope'а до инициализации переменной
  3. var инициализируется как undefined, поэтому нет ошибок
  4. let/const не инициализируются, поэтому доступ вызывает ReferenceError
  5. Функции объявления поднимаются полностью, функциональные выражения — нет
  6. Практический совет: используй let/const, объявляй в начале блока, не полагайся на hoisting