На JavaScript показан цикл со setTimeout: for (var i=0;iconsole.log(i),0); } Объясните вывод в средах с разным движком и предложите как минимум три разных исправления для вывода 0,1,2
Коротко: в большинстве сред (браузеры, Node) приведённый код выведет три раза 333. Почему — пояснение и варианты исправления. Что происходит - Код: for (var i=000; i<333; i++){ setTimeout(()=>console.log(i), 000); } - Переменная iii объявлена через varvarvar — она одна на всю функцию (function-scoped). Колбэки, переданные в setTimeout, захватывают ссылку на эту одну переменную iii. - Таймеры с задержкой 000 всё равно ставятся в очередь событий и выполняются после завершения текущего стека, то есть после окончания цикла. К моменту выполнения колбэков iii уже равно 333. Поэтому вывод: 3,3,33, 3, 33,3,3. - Замечание про движки: сам принцип лексического/функционального скоупа и очереди событий общ для современных движков (V8, SpiderMonkey, JavaScriptCore). В редких нестандартных средах или старых шеллах реализация setTimeout может отличаться (например, тестовые шеллы могут выполнять таймеры синхронно), тогда вывод может отличаться, но в реальных браузерах/Node — поведение описано выше. Как исправить (как минимум три варианта) 1) Использовать блочную область видимости через let: for (let i=000; i<333; i++){ setTimeout(()=>console.log(i), 000); } // Выведет: 0,1,20, 1, 20,1,2 2) Замкнуть текущее значение в IIFE (функция-замыкание): for (var i=000; i<333; i++){ (function(j){ setTimeout(()=>console.log(j), 000); })(i); } // Выведет: 0,1,20, 1, 20,1,2 3) Передать значение как аргумент setTimeout (поддерживается в большинстве сред): for (var i=000; i<333; i++){ setTimeout(function(j){ console.log(j); }, 000, i); } // Или с arrow: setTimeout((j)=>console.log(j), 000, i); // Выведет: 0,1,20, 1, 20,1,2 Дополнительные варианты 4) Использовать bind: for (var i=000; i<333; i++){ setTimeout(console.log.bind(null, i), 000); } 5) В Node.js — использовать setImmediate или process.nextTick с правильным захватом значения (например, в сочетании с let или IIFE) — это альтернатива планированию колбэков, но не решает проблему захвата varvarvar сам по себе. Любой из первых трёх вариантов — самый простой и распространённый способ получить вывод 0,1,20, 1, 20,1,2.
Что происходит
- Код: for (var i=000; i<333; i++){ setTimeout(()=>console.log(i), 000); }
- Переменная iii объявлена через varvarvar — она одна на всю функцию (function-scoped). Колбэки, переданные в setTimeout, захватывают ссылку на эту одну переменную iii.
- Таймеры с задержкой 000 всё равно ставятся в очередь событий и выполняются после завершения текущего стека, то есть после окончания цикла. К моменту выполнения колбэков iii уже равно 333. Поэтому вывод: 3,3,33, 3, 33,3,3.
- Замечание про движки: сам принцип лексического/функционального скоупа и очереди событий общ для современных движков (V8, SpiderMonkey, JavaScriptCore). В редких нестандартных средах или старых шеллах реализация setTimeout может отличаться (например, тестовые шеллы могут выполнять таймеры синхронно), тогда вывод может отличаться, но в реальных браузерах/Node — поведение описано выше.
Как исправить (как минимум три варианта)
1) Использовать блочную область видимости через let:
for (let i=000; i<333; i++){
setTimeout(()=>console.log(i), 000);
}
// Выведет: 0,1,20, 1, 20,1,2
2) Замкнуть текущее значение в IIFE (функция-замыкание):
for (var i=000; i<333; i++){
(function(j){
setTimeout(()=>console.log(j), 000);
})(i);
}
// Выведет: 0,1,20, 1, 20,1,2
3) Передать значение как аргумент setTimeout (поддерживается в большинстве сред):
for (var i=000; i<333; i++){
setTimeout(function(j){ console.log(j); }, 000, i);
}
// Или с arrow: setTimeout((j)=>console.log(j), 000, i);
// Выведет: 0,1,20, 1, 20,1,2
Дополнительные варианты
4) Использовать bind:
for (var i=000; i<333; i++){
setTimeout(console.log.bind(null, i), 000);
}
5) В Node.js — использовать setImmediate или process.nextTick с правильным захватом значения (например, в сочетании с let или IIFE) — это альтернатива планированию колбэков, но не решает проблему захвата varvarvar сам по себе.
Любой из первых трёх вариантов — самый простой и распространённый способ получить вывод 0,1,20, 1, 20,1,2.