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

Определить вывод кода с замыканиями и циклом

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

Условие

Определите, что выведет следующий код, и объясните почему.

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

Задание

  1. Объясните, почему код выводит именно такой результат
  2. Предложите как минимум 3 способа исправить код, чтобы он выводил 0, 1, 2

Подсказка

Подумайте о разнице между var и let, а также о способах создания отдельной области видимости для каждой итерации.

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Решение

Эта классическая задача на понимание замыканий, области видимости (var vs let) и асинхронного выполнения JavaScript.

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

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

// Результат: 3 3 3

Почему 3, 3, 3?

  1. Цикл выполняется синхронно, переменная i увеличивается до 3
  2. setTimeout() выполняется асинхронно (через 1000мс)
  3. К моменту выполнения callback'ов цикл уже завершён, i = 3
  4. Все три callback'а ссылаются на одну переменную i в глобальной области видимости
  5. Поэтому все выводят 3

Способ 1: Использовать let вместо var

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

// Результат: 0 1 2

Почему работает:

  • let создаёт новую область видимости для каждой итерации
  • Каждый callback получает свою переменную i
  • Это рекомендуемый современный подход

Способ 2: Замыкание через IIFE

for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 1000);
  })(i);
}

// Результат: 0 1 2

Как работает:

  • IIFE (Immediately Invoked Function Expression) создаёт новую область видимости
  • Параметр j получает текущее значение i
  • Каждое замыкание захватывает свой j

Способ 3: Замыкание через функцию-генератор

function createLogger(value) {
  return function() {
    console.log(value);
  };
}

for (var i = 0; i < 3; i++) {
  setTimeout(createLogger(i), 1000);
}

// Результат: 0 1 2

Как работает:

  • createLogger() создаёт новое замыкание с уникальным value
  • Каждое замыкание захватывает свой value

Способ 4: Использовать bind()

for (var i = 0; i < 3; i++) {
  setTimeout((function(j) {
    console.log(j);
  }).bind(null, i), 1000);
}

// Результат: 0 1 2

Как работает:

  • bind() создаёт новую функцию с привязанными параметрами
  • Каждой функции привязывается текущее значение i

Способ 5: Arrow function с параметром

for (var i = 0; i < 3; i++) {
  setTimeout((i) => {
    console.log(i);
  }, 1000, i);
}

// Результат: 0 1 2

Как работает:

  • setTimeout принимает аргументы после задержки
  • Arrow function получает параметр i
  • Создаётся новое замыкание для каждой итерации

Способ 6: Массив и forEach

Array.from({length: 3}, (_, i) => {
  setTimeout(function() {
    console.log(i);
  }, 1000);
});

// Результат: 0 1 2

Как работает:

  • forEach создаёт новую область видимости для каждой итерации
  • Параметр i автоматически локален

Сравнение решений

СпособПростотаСовременностьРекомендация
let✅✅✅✅Лучше всего
IIFE📊Legacy code
Функция📊Functional
bind()📊OK
Arrow + параметр📊OK
forEachДля массивов

Key Concepts

  1. var — функциональная область видимости (function-scoped)
  2. let — блочная область видимости (block-scoped)
  3. Замыкание — функция, которая помнит переменные из внешней области
  4. setTimeout — асинхронный, выполняется после завершения синхронного кода
  5. Стек вызовов — цикл завершается ДО выполнения setTimeout callback'ов

Event Loop объяснение

Синхронный код:
  for (var i = 0; i < 3; i++) {  // i = 0, 1, 2, потом 3
    setTimeout(...);  // Добавляет в queue
  }
  // Цикл завершился, i = 3

Асинхронный код (через 1000мс):
  console.log(i);  // i = 3 (одна переменная для всех!)
  console.log(i);  // i = 3
  console.log(i);  // i = 3

Рекомендации для собеседования

  1. Объясните проблему с var и hoisting
  2. Покажите let как современное решение
  3. Объясните IIFE для лучшего понимания замыканий
  4. Обсудите Event Loop и асинхронность
  5. Покажите разницу var vs let vs const

Лучший ответ для production: Использовать let или const - это стандарт ES6, самый читаемый и безопасный.