← Все вопросы

Почему setTimeout в цикле for печатает одно и то же число в JavaScript?

Задан 6 месяцев назад434 просмотров2 ответа
11

Запускаю в цикле for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 100) — ожидаю 0,1,2, а печатается 3,3,3. Почему setTimeout в цикле выводит одно число и как получить нормальную последовательность?

2 ответа

15
✓ Принятый ответ — помог автору

Это знаменитая ловушка с var. Дело в двух вещах: var имеет одну общую переменную на весь цикл (нет блочной области видимости), а setTimeout асинхронный — колбэки выполнятся уже после того, как цикл завершился.

К моменту срабатывания таймеров цикл давно закончился, и i равно 3. Все три колбэка смотрят на одну и ту же переменную i, поэтому печатают 3, 3, 3.

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

Решение 1 — заменить var на let (самое простое и правильное). let создаёт новую переменную на каждой итерации, и каждый колбэк запоминает своё значение:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2

Решение 2 — передать i как аргумент setTimeout:

for (var i = 0; i < 3; i++) {
  setTimeout((j) => console.log(j), 100, i);
}
// 0, 1, 2

Решение 3 — IIFE (замыкание со своей копией), старый трюк до появления let:

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

Вывод: почти всегда используй let/const вместо var — эта проблема исчезает сама. Это одна из главных причин, почему var считают устаревшим.

6

Суть в одном предложении: var — одна переменная на всех, let — отдельная на каждую итерацию.

То же самое всплывает не только с setTimeout, но и с любыми отложенными колбэками — например, обработчиками addEventListener, навешанными в цикле. Везде лечится переходом на let. Если видишь в цикле var рядом с асинхронным колбэком — почти наверняка там притаился этот баг.

Ваш ответ

Войдите, чтобы ответить на вопрос.