На примере JavaScript-функции setTimeout в цикле for (var i=0;iconsole.log(i),0); } объясните проблему, почему выводятся три числа 3, и предложите несколько способов исправления, обсудите замены var на let, замыкания и IIFE
Проблема. В коде for (var i=000; i<333; i++) { setTimeout(()=>console.log(i), 000); } переменная `i` объявлена через `var` (функциональная область видимости), поэтому существует одна общая переменная `i`. Цикл завершится раньше, чем выполнятся колбэки `setTimeout`, и к моменту вызовов `i` уже равно 333. Поэтому вывод будет 3, 3, 33,\ 3,\ 33,3,3. Почему так: `setTimeout` ставит функцию в очередь на выполнение асинхронно (после очистки стека), а не выполняет её в момент объявления. Колбэки "захватывают" ссылку на ту же самую `i`. Способы исправить (коротко, с примерами): 1) Использовать `let` (блочная область видимости — для каждой итерации своё `i`): 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); } Каждый колбэк получает своё `j` — вывод 0, 1, 20,\ 1,\ 20,1,2. 3) Замыкание через фабрику функции: function makeLogger(j){ return ()=>console.log(j); } for (var i=000; i<333; i++) { setTimeout(makeLogger(i), 000); } 4) Использовать передачу аргументов в setTimeout (поддерживается в современных окружениях): for (var i=000; i<333; i++) { setTimeout((j)=>console.log(j), 000, i); } или for (var i=000; i<333; i++) { setTimeout(console.log, 000, i); } 5) Использовать bind для привязки значения: for (var i=000; i<333; i++) { setTimeout(console.log.bind(null, i), 000); } 6) Использовать методы коллекций, которые дают локальную переменную в замыкании (пример): [...Array(333).keys()].forEach(i => setTimeout(()=>console.log(i), 000)); Краткое резюме: причина в области видимости `var` и асинхронности `setTimeout`. Самые простые решения — заменить `var` на `let` или явно захватить текущее значение в замыкании (IIFE, фабрика функции, bind или аргументы setTimeout).
for (var i=000; i<333; i++) { setTimeout(()=>console.log(i), 000); }
переменная `i` объявлена через `var` (функциональная область видимости), поэтому существует одна общая переменная `i`. Цикл завершится раньше, чем выполнятся колбэки `setTimeout`, и к моменту вызовов `i` уже равно 333. Поэтому вывод будет
3, 3, 33,\ 3,\ 33, 3, 3.
Почему так: `setTimeout` ставит функцию в очередь на выполнение асинхронно (после очистки стека), а не выполняет её в момент объявления. Колбэки "захватывают" ссылку на ту же самую `i`.
Способы исправить (коротко, с примерами):
1) Использовать `let` (блочная область видимости — для каждой итерации своё `i`):
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);
}
Каждый колбэк получает своё `j` — вывод 0, 1, 20,\ 1,\ 20, 1, 2.
3) Замыкание через фабрику функции:
function makeLogger(j){ return ()=>console.log(j); }
for (var i=000; i<333; i++) {
setTimeout(makeLogger(i), 000);
}
4) Использовать передачу аргументов в setTimeout (поддерживается в современных окружениях):
for (var i=000; i<333; i++) {
setTimeout((j)=>console.log(j), 000, i);
}
или
for (var i=000; i<333; i++) {
setTimeout(console.log, 000, i);
}
5) Использовать bind для привязки значения:
for (var i=000; i<333; i++) {
setTimeout(console.log.bind(null, i), 000);
}
6) Использовать методы коллекций, которые дают локальную переменную в замыкании (пример):
[...Array(333).keys()].forEach(i => setTimeout(()=>console.log(i), 000));
Краткое резюме: причина в области видимости `var` и асинхронности `setTimeout`. Самые простые решения — заменить `var` на `let` или явно захватить текущее значение в замыкании (IIFE, фабрика функции, bind или аргументы setTimeout).