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

Как происходит всплытие Let?

1.7 Middle🔥 201 комментариев
#JavaScript Core

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

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

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

Как происходит всплытие (hoisting) переменных let в JavaScript

Что такое hoisting

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

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

Hoisting для let/const (Temporal Dead Zone)

// let и const ДЕЙСТВИТЕЛЬНО всплывают, но в другом виде

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

// ЧТО ПРОИСХОДИТ:
// 1. Фаза парсинга: JavaScript видит объявление let x
// 2. Hoisting: x всплывает в верхнюю часть scope
// 3. НО x инициализируется как "uninitialized" (не undefined!)
// 4. Фаза выполнения: код начинает выполняться
// 5. console.log(x) пытается доступ к неинициализированному x
// 6. ReferenceError!
// 7. let x = 5 выполняется, x инициализируется
// 8. Теперь x = 5

Temporal Dead Zone (TDZ)

TDZ — это период между началом выполнения scope и выполнением строки let x = value

// ПРИМЕР 1: Ошибка в TDZ
function example() {
  console.log(y); // ← TDZ: y всплыл, но не инициализирован
  // ReferenceError: Cannot access 'y' before initialization
  
  let y = 10; // ← Здесь y инициализируется, TDZ заканчивается
  console.log(y); // 10 (OK)
}

// ПРИМЕР 2: Правильное использование
function example2() {
  let z = 10; // Инициализируется сразу
  console.log(z); // 10 (OK)
}

// ПРИМЕР 3: TDZ с условиями
function example3(condition) {
  if (condition) {
    console.log(a); // ReferenceError!
    // a был объявлен ниже, поэтому он в TDZ
    let a = 5;
  }
}

Отличие от var (старое поведение)

// VAR — всплывает И инициализируется как undefined

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

// Что происходит с var:
// 1. Hoisting: var x всплывает
// 2. Инициализация: x = undefined (по умолчанию)
// 3. console.log(x) → undefined
// 4. var x = 5 выполняется
// 5. x = 5

// LET/CONST — всплывают, но БЕЗ инициализации (TDZ)

console.log(y); // ReferenceError (ошибка!)
let y = 5;

// Что происходит с let:
// 1. Hoisting: let y всплывает
// 2. БЕЗ инициализации (TDZ начинается)
// 3. console.log(y) → ReferenceError
// 4. let y = 5 выполняется
// 5. y = 5

Визуализация hoisting для let

// ===== КОД В ФАЙЛЕ =====
console.log(username);
let username = "John";

// ===== ПАРСИНГ (фаза 1) =====
// JavaScript парсер видит все let, const, var, function declarations
// let username всплывает в верхнюю часть scope
// Но НЕ инициализируется

// ===== ВЫПОЛНЕНИЕ (фаза 2) =====
// let username; // ← Объявление всплыло, но БЕЗ значения
// console.log(username); // ReferenceError!
// username = "John"; // Теперь инициализируется

// ===== РЕЗУЛЬТАТ =====
// ReferenceError: Cannot access 'username' before initialization

Hoisting в разных скопах (block scope)

// let имеет BLOCK SCOPE (не function scope, как var)

function example() {
  console.log(x); // ReferenceError
  
  { // Новый block scope
    let x = 5; // x объявлен в этом блоке
  }
  
  console.log(x); // ReferenceError (x не существует вне блока)
}

// ВАЖНО: TDZ начинается с начала БЛОКА, не с начала функции!

function example2() {
  console.log(y); // ReferenceError
  // y в TDZ от начала этого блока
  
  let y = 10;
  console.log(y); // 10
}

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

// ОШИБКА 1: Использование до объявления
function test1() {
  console.log(count); // ReferenceError
  let count = 0;
}

// ОШИБКА 2: Использование в условии
function test2() {
  if (user) { // ReferenceError: user в TDZ
    let user = "John";
  }
}

// ОШИБКА 3: Использование в цикле
function test3() {
  for (let i = 0; i < 10; i++) {
    if (i === 0) {
      console.log(j); // ReferenceError
      let j = 5; // j объявлен, но в TDZ
    }
  }
}

// ПРАВИЛЬНО: сначала объявить, потом использовать
function test4() {
  let value = 10; // Инициализируется
  console.log(value); // 10 (OK)
}

Hoisting для const

// const ведёт себя ТОЧНО как let (TDZ существует)

console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14159;

// const также имеет block scope

function example() {
  console.log(MAX); // ReferenceError (TDZ)
  
  const MAX = 100;
  console.log(MAX); // 100
}

Hoisting в функциях

// Функции всплывают ПОЛНОСТЬЮ (объявление и тело)

console.log(sayHi()); // "Hi!" (OK, функция полностью всплыла)

function sayHi() {
  return "Hi!";
}

// НО стрелочные функции (как переменные) подчиняются TDZ

console.log(greet()); // ReferenceError
const greet = () => "Hello"; // const, значит TDZ

Использование переменных до объявления

// ПРИМЕР: Цепочка зависимостей с TDZ

function process() {
  // ===== TDZ для x и y =====
  
  console.log(x + y); // ReferenceError: Cannot access 'x' before initialization
  // Даже если x определён ниже y, ОБА в TDZ
  
  let x = 5;
  let y = 10;
  
  console.log(x + y); // 15 (OK)
}

// СЛОЖНЫЙ ПРИМЕР: nested scopes

function outer() {
  console.log(a); // ReferenceError (a в TDZ внешнего scope)
  
  {
    // Новый block scope
    console.log(a); // ReferenceError (a в TDZ внутреннего scope)
    let a = 5;
    console.log(a); // 5
  }
  
  let a = 10;
  console.log(a); // 10
}

Как отладить TDZ ошибки

// ✅ ПРАВИЛЬНО: использовать переменную после объявления
let name;
console.log(name); // undefined (объявлена, но не инициализирована)
name = "John";
console.log(name); // "John"

// ✅ ПРАВИЛЬНО: инициализировать сразу
let age = 30;
console.log(age); // 30

// ❌ НЕПРАВИЛЬНО: использовать до объявления
console.log(city); // ReferenceError
let city = "NYC";

// Решение: переместить объявление выше или использовать позже
let city = "NYC"; // Сначала объявляем
console.log(city); // Потом используем

Важные принципы

// ПРАВИЛО 1: let и const всплывают, но в TDZ
let x; // Объявлено в TDZ
console.log(x); // undefined (инициализировано как undefined)
x = 5; // Присвоено значение

// ПРАВИЛО 2: TDZ длится от начала scope до строки объявления
function fn() {
  // TDZ начинается здесь
  console.log(y); // ReferenceError (в TDZ)
  let y = 10; // TDZ заканчивается
  console.log(y); // 10
}

// ПРАВИЛО 3: Block scope создаёт новый TDZ
let a = 1;
{
  // Новый block scope
  console.log(a); // ReferenceError (a в TDZ этого блока)
  let a = 2; // Новая переменная a в этом блоке
}
console.log(a); // 1 (внешняя переменная)

// ПРАВИЛО 4: var ведёт себя иначе (нет TDZ)
console.log(b); // undefined
var b = 5;

Сравнение hoisting для var, let, const

┌──────────┬────────────────┬────────────────────┬────────────────┐
│ Тип      │ Всплывает      │ Инициализируется   │ TDZ            │
├──────────┼────────────────┼────────────────────┼────────────────┤
│ var      │ ✓ (да)         │ ✓ (undefined)      │ ✗ (нет)        │
│ let      │ ✓ (да)         │ ✗ (нет)            │ ✓ (да, TDZ)    │
│ const    │ ✓ (да)         │ ✗ (нет)            │ ✓ (да, TDZ)    │
│ function │ ✓ (полностью)  │ ✓ (всё тело)       │ ✗ (нет)        │
└──────────┴────────────────┴────────────────────┴────────────────┘

Итог

Let всплывает (hoisting происходит), но НЕ инициализируется:

  1. Объявление всплывает в верхнюю часть scope
  2. TDZ (Temporal Dead Zone) начинается с начала scope
  3. Использование в TDZ вызывает ReferenceError
  4. После строки объявления переменная инициализируется
  5. После инициализации переменная используется нормально

Отличие от var:

  • var инициализируется как undefined (нет ошибки)
  • let/const вызывают ошибку при доступе в TDZ

Вывод: всегда объявляй let/const ДО их использования, чтобы избежать ReferenceError!

Как происходит всплытие Let? | PrepBro