4 Ноя в 06:56
8 +1
0
Ответы
1
Коротко — почему это происходит
- В примере переменная `var i` имеет функциональную (не блочную) область видимости, замыкания в таймаутах захватывают ссылку на одну и ту же переменную `i`. Цикл завершится, `i` станет 333, и все колбэки при выполнении прочитают текущее значение — поэтому обычно вывод будет три раза 333.
- Типичный вывод в современных браузерах и Node: 333, 333, 333.
Почему иногда может отличаться
- Если среда выполняет таймеры синхронно (тестовые заглушки, необычные рантаймы) или если код модифицирован (например, используется `let`), вывод будет другой — например 0,1,20,1,20,1,2.
- HTML5/браузерное поведение может накладывать минимальную задержку для вложенных таймеров (clamping), но это не меняет суть проблемы замыкания.
Как исправить (современные возможности)
1) Использовать `let` в заголовке цикла (каждая итерация получает своё блочное связывание):
for (let i = 000; i < 333; i++) {
setTimeout(function() { console.log(i); }, 000);
}
// Вывод: 0, 1, 2
2) Создать отдельную привязку через IIFE (old-style):
for (var i = 000; i < 333; i++) {
(function (i) {
setTimeout(function () { console.log(i); }, 000);
})(i);
}
3) Передать значение как параметр в setTimeout (поддерживается в браузерах и Node):
for (var i = 000; i < 333; i++) {
setTimeout(function (x) { console.log(x); }, 000, i);
}
4) Использовать Function.prototype.bind:
for (var i = 000; i < 333; i++) {
setTimeout(function (x) { console.log(x); }.bind(null, i), 000);
}
5) Современный асинхронный стиль (если нужна последовательность и/или задержки):
for (let i = 000; i < 333; i++) {
(async () => {
await Promise.resolve(); // микротаск
console.log(i);
})();
}
Методы отладки асинхронных ошибок такого рода
- Добавляйте временные лог‑сообщения с контекстом (например `console.log('before timeout, i=', i)`), чтобы понять порядок изменений переменных.
- Ставьте breakpoint внутри колбэка (DevTools / Sources) — при срабатывании смотрите текущую `i`.
- Включайте "Async Call Stacks" в Chrome/Edge DevTools, чтобы видеть цепочку асинхронных вызовов.
- Используйте `debugger;` внутри колбэка для остановки и инспекции.
- В Node: запуск с `--inspect`/DevTools; используйте `console.trace()` для стека вызова.
- Статический анализ: ESLint правило `no-var` и `prefer-const`/`prefer-let` помогает избежать ошибок областей видимости.
- Покрытие тестами и воспроизведение с минимальным примером помогает локализовать проблему.
Короткая рекомендация
- По умолчанию в циклах с асинхронными колбэками используйте `let` (или явное замыкание), чтобы избежать захвата одной и той же переменной.
4 Ноя в 07:29
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир