Разберите поведение и исправьте баг в этом JavaScript: for (var i = 0; i console.log(i), 100); } Почему вывод не 0,1,2 и какие варианты исправления есть (ES6, IIFE, bind и т.д.)?
Проблема: переменная `i` объявлена через `var` — она имеет функциональную (не блочную) область видимости. Цикл выполняется быстро и к моменту срабатывания таймаутов значение `i` становится 333. Поэтому все колбэки видят одно и то же последнее значение и выводят не 0,1,20,1,20,1,2, а 3,3,33,3,33,3,3. Варианты исправления (коротко, с примерами): 1) ES6 `let` (блочная область для каждой итерации) ```js for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } ``` Выведет 0,1,20,1,20,1,2. 2) IIFE (замыкание, фиксирующее текущее значение) ```js for (var i = 0; i < 3; i++) { (function (j) { setTimeout(() => console.log(j), 100); })(i); } ``` Здесь `j` захватывает текущее `i`, вывод будет 0,1,20,1,20,1,2. 3) bind (привязать аргумент к функции) ```js for (var i = 0; i < 3; i++) { setTimeout(console.log.bind(console, i), 100); } ``` `bind` создаёт функцию с фиксированным аргументом, вывод 0,1,20,1,20,1,2. 4) Передать аргументы в setTimeout (в средах, где поддерживается) ```js for (var i = 0; i < 3; i++) { setTimeout((x) => console.log(x), 100, i); } // или for (var i = 0; i < 3; i++) { setTimeout(console.log, 100, i); } ``` Третий аргумент передаётся в колбэк; результат 0,1,20,1,20,1,2. 5) Создать фабрику колбэков (closure-returning function) ```js function makeLogger(x) { return () => console.log(x); } for (var i = 0; i < 3; i++) { setTimeout(makeLogger(i), 100); } ``` 6) Использовать методы массива / forEach (локальная переменная в колбэке) ```js [0,1,2].forEach(i => setTimeout(() => console.log(i), 100)); ``` Краткая рекомендация: в современном коде проще и яснее использовать `let` в цикле.
Варианты исправления (коротко, с примерами):
1) ES6 `let` (блочная область для каждой итерации)
```js
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
```
Выведет 0,1,20,1,20,1,2.
2) IIFE (замыкание, фиксирующее текущее значение)
```js
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 100);
})(i);
}
```
Здесь `j` захватывает текущее `i`, вывод будет 0,1,20,1,20,1,2.
3) bind (привязать аргумент к функции)
```js
for (var i = 0; i < 3; i++) {
setTimeout(console.log.bind(console, i), 100);
}
```
`bind` создаёт функцию с фиксированным аргументом, вывод 0,1,20,1,20,1,2.
4) Передать аргументы в setTimeout (в средах, где поддерживается)
```js
for (var i = 0; i < 3; i++) {
setTimeout((x) => console.log(x), 100, i);
}
// или
for (var i = 0; i < 3; i++) {
setTimeout(console.log, 100, i);
}
```
Третий аргумент передаётся в колбэк; результат 0,1,20,1,20,1,2.
5) Создать фабрику колбэков (closure-returning function)
```js
function makeLogger(x) {
return () => console.log(x);
}
for (var i = 0; i < 3; i++) {
setTimeout(makeLogger(i), 100);
}
```
6) Использовать методы массива / forEach (локальная переменная в колбэке)
```js
[0,1,2].forEach(i => setTimeout(() => console.log(i), 100));
```
Краткая рекомендация: в современном коде проще и яснее использовать `let` в цикле.