Объясните поведение и результат этого JavaScript‑фрагмента, укажите, почему так происходит, и предложите как минимум два способа исправить его, чтобы получить ожидаемый вывод 0,1,2 (один способ с ES5 и один с ES6+): for (var i = 0; i console.log(i), 10); }
Коротко: фрагмент выведет три раза 333 (т.е. 3, 3, 3\,3,\;3,\;33,3,3), потому что все колбэки замыкают одну переменную iii, а таймауты выполняются после завершения цикла, когда iii стало 333. Ожидаемый вывод — 0, 1, 2\,0,\,1,\,20,1,2. Почему так происходит: - `var` имеет функциональную (не блочную) область видимости, поэтому в каждой итерации используется одна и та же переменная `i`. - стрелочные функции/функции внутри `setTimeout` замыкают ссылку на эту переменную, а не её значение в момент создания. - когда таймауты срабатывают (через 10\,1010 мс), цикл уже завершён и `i === 3`. Способы исправить. ES5 (IIFE — замыкание на копии значения): for (var i = 0; i < 3; i++) { (function(iCopy) { setTimeout(function() { console.log(iCopy); }, 10); })(i); } ES6+ (блочная переменная `let`, новая привязка на каждую итерацию): for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 10); } Доп. альтернативы: - Передать значение как аргумент `setTimeout` (поддерживается в средах Node/браузерах): for (var i = 0; i < 3; i++) { setTimeout(function(x) { console.log(x); }, 10, i); } - Использовать `bind`: for (var i = 0; i < 3; i++) { setTimeout(console.log.bind(null, i), 10); } Все приведённые исправления дадут ожидаемый вывод 0, 1, 2\,0,\,1,\,20,1,2.
Почему так происходит:
- `var` имеет функциональную (не блочную) область видимости, поэтому в каждой итерации используется одна и та же переменная `i`.
- стрелочные функции/функции внутри `setTimeout` замыкают ссылку на эту переменную, а не её значение в момент создания.
- когда таймауты срабатывают (через 10\,1010 мс), цикл уже завершён и `i === 3`.
Способы исправить.
ES5 (IIFE — замыкание на копии значения):
for (var i = 0; i < 3; i++) {
(function(iCopy) {
setTimeout(function() { console.log(iCopy); }, 10);
})(i);
}
ES6+ (блочная переменная `let`, новая привязка на каждую итерацию):
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 10);
}
Доп. альтернативы:
- Передать значение как аргумент `setTimeout` (поддерживается в средах Node/браузерах):
for (var i = 0; i < 3; i++) {
setTimeout(function(x) { console.log(x); }, 10, i);
}
- Использовать `bind`:
for (var i = 0; i < 3; i++) {
setTimeout(console.log.bind(null, i), 10);
}
Все приведённые исправления дадут ожидаемый вывод 0, 1, 2\,0,\,1,\,20,1,2.