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

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

2.0 Middle🔥 173 комментариев
#JavaScript Core

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

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

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

Hoisting переменных: всплытие var

Всплытие (hoisting) - это поведение JavaScript, при котором объявления переменных и функций "поднимаются" на верх области видимости. Это важная концепция, которая часто вызывает путаницу.

1. Как работает Hoisting

JavaScript парсер проходит код в два этапа:

Этап 1: Компиляция - интерпретатор сканирует код и регистрирует все объявления переменных и функций в памяти.

Этап 2: Выполнение - построчное выполнение кода.

После этапа компиляции, когда мы обращаемся к переменной, она уже существует в памяти.

2. Hoisting с var

Переменные, объявленные с помощью var, поднимаются И инициализируются значением undefined:

// Это выглядит так:
console.log(x); // undefined
var x = 5;
console.log(x); // 5

// На самом деле интерпретатор видит это как:
var x; // Объявление поднято
console.log(x); // undefined - переменная существует, но не инициализирована
x = 5; // Инициализация
console.log(x); // 5

3. Функциональная область видимости var

var имеет функциональную область видимости, поэтому она всплывает на верх функции, не на верх блока:

function example() {
  console.log(y); // undefined - не ReferenceError!
  
  if (true) {
    var y = 10; // var всплывает на верх функции
  }
  
  console.log(y); // 10 - доступна везде в функции
}

example();

// На самом деле это выглядит как:
function example() {
  var y; // Поднято на верх функции
  console.log(y); // undefined
  
  if (true) {
    y = 10;
  }
  
  console.log(y); // 10
}

4. Проблемы с var и hoisting

hoisting var часто приводит к ошибкам и неожиданному поведению:

// Проблема 1: случайное переопределение
var result = 'initial';

if (someCondition) {
  var result = 'modified'; // Это переопределяет внешний result!
}

console.log(result); // Может быть 'modified' или 'initial' в зависимости от условия

// Проблема 2: в циклах
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 3, 3, 3 - i всплыла из цикла
  }, 100);
}

// Проблема 3: глобальная область видимости
function test() {
  if (true) {
    var global = 'value';
  }
}
test();
console.log(global); // 'value' - случайно в глобальной области!

5. Hoisting с let и const

let и const также поднимаются, но их поведение отличается. Они создают "Temporal Dead Zone" (TDZ) - период перед объявлением, когда переменная недоступна:

// Temporal Dead Zone (TDZ)
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5; // Инициализация

console.log(y); // ReferenceError: Cannot access 'y' before initialization
const y = 10; // Инициализация

// На самом деле это выглядит как:
// TDZ начинается
console.log(x); // ReferenceError - переменная в TDZ
let x = 5; // TDZ заканчивается, переменная инициализирована

6. Hoisting функций

Функции, объявленные с помощью function declaration, полностью всплывают, включая тело:

// Это работает!
console.log(add(2, 3)); // 5

function add(a, b) {
  return a + b;
}

// На самом деле интерпретатор видит это как:
function add(a, b) {
  return a + b;
}
console.log(add(2, 3)); // 5

// А вот это не работает
console.log(subtract(5, 3)); // TypeError: subtract is not a function

var subtract = function(a, b) { // Только переменная всплывает
  return a - b;
};

// На самом деле:
var subtract; // Объявление переменной всплывает
console.log(subtract); // undefined
subtract = function(a, b) { // Присваивание не всплывает
  return a - b;
};

7. Примеры всплытия в реальном коде

Проблема с var в обработчиках событий:

for (var i = 0; i < 5; i++) {
  document.getElementById(`btn${i}`).addEventListener('click', function() {
    console.log(i); // Всегда выведет 5
  });
}

// Решение 1: использовать let
for (let i = 0; i < 5; i++) {
  document.getElementById(`btn${i}`).addEventListener('click', function() {
    console.log(i); // Выведет правильное значение: 0, 1, 2, 3, 4
  });
}

// Решение 2: замыкание с var
for (var i = 0; i < 5; i++) {
  (function(index) {
    document.getElementById(`btn${index}`).addEventListener('click', function() {
      console.log(index); // Выведет правильное значение
    });
  })(i);
}

Проблема с var и асинхронным кодом:

var functions = [];

for (var i = 0; i < 3; i++) {
  functions.push(function() {
    return i; // Все функции вернут 3
  });
}

console.log(functions[0]()); // 3 (ожидали 0)
console.log(functions[1]()); // 3 (ожидали 1)
console.log(functions[2]()); // 3 (ожидали 2)

// Решение с let:
var functions2 = [];

for (let i = 0; i < 3; i++) {
  functions2.push(function() {
    return i; // Каждая функция имеет свою переменную i
  });
}

console.log(functions2[0]()); // 0
console.log(functions2[1]()); // 1
console.log(functions2[2]()); // 2

8. Порядок hoisting

Когда в области видимости есть и переменная, и функция с одним именем:

foo(); // 'I am function'
console.log(typeof foo); // 'function'

var foo = 'I am variable';

function foo() {
  console.log('I am function');
}

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

// На самом деле интерпретатор видит это как:
function foo() { // Функция всплывает первой
  console.log('I am function');
}
var foo; // Объявление переменной игнорируется (уже существует)

foo(); // 'I am function'
console.log(typeof foo); // 'function'

foo = 'I am variable'; // Переопределение
console.log(typeof foo); // 'string'

9. Практические рекомендации

1. Избегайте var в новом коде:

// Плохо
for (var i = 0; i < 10; i++) {
  var item = items[i];
  console.log(item);
}

// Хорошо
for (let i = 0; i < 10; i++) {
  const item = items[i];
  console.log(item);
}

2. Объявляйте переменные в начале области видимости:

// Плохо
function process(data) {
  if (data) {
    var result = data.map(x => x * 2);
    return result;
  }
}

// Хорошо
function process(data) {
  const result = data ? data.map(x => x * 2) : [];
  return result;
}

3. Используйте const по умолчанию:

// Константы
const MAX_RETRIES = 3;
const API_ENDPOINT = 'https://api.example.com';

// Переменные (когда нужно менять)
let counter = 0;
let isLoading = false;

Резюме

  1. var всплывает с инициализацией undefined
  2. let и const всплывают, но находятся в TDZ до объявления
  3. Функции полностью всплывают
  4. var имеет функциональную область видимости, что часто приводит к ошибкам
  5. Используйте let/const вместо var в современном коде
  6. Помните о hoisting при работе с циклами и асинхронным кодом
Как происходит всплытие Var? | PrepBro