Рассмотрите JavaScript-кейс: for (var i = 0; i console.log(i), 100); } — объясните, почему часто получают 3,3,3, какие концепции замыканий и области видимости здесь задействованы и как исправить код разными способами
Коротко: вы получаете 3,3,33,3,33,3,3 потому, что все три колбэка захватывают одну и ту же переменную iii (область видимости `var` — функция), цикл завершается до выполнения `setTimeout`, и к моменту вызова колбэков iii уже равно 333. Это поведение замыканий + модель событий (event loop / макротаски). Пояснения по концепциям - Замыкание: колбэк сохраняет ссылку на переменную iii, а не её моментальное значение. Значение берётся при выполнении колбэка (после цикла). - Область видимости `var`: одна переменная iii для всего цикла (function-scoped), поэтому все итерации используют одинаковую переменную. - Event loop / асинхронность: `setTimeout(..., 100)` ставит колбэк в очередь; основной цикл завершится раньше, чем таймеры сработают. Пример проблемы for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Выведет: 3, 3, 3 Как исправить (несколько способов) 1) Использовать `let` (ES6, блоковая область) for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Выведет: 0, 1, 2 2) Захватить значение через IIFE (устаревший способ) for (var i = 0; i < 3; i++) { (function(j) { setTimeout(() => console.log(j), 100); })(i); } // Выведет: 0, 1, 2 3) Создать замыкание через фабрику функций function makeLogger(j) { return () => console.log(j); } for (var i = 0; i < 3; i++) { setTimeout(makeLogger(i), 100); } 4) Использовать `bind` for (var i = 0; i < 3; i++) { setTimeout(console.log.bind(null, i), 100); } 5) Передать аргументы в `setTimeout` (поддерживается в браузерах/Node) for (var i = 0; i < 3; i++) { setTimeout((v) => console.log(v), 100, i); } // или for (var i = 0; i < 3; i++) { setTimeout(console.log, 100, i); // осторожно с контекстом `this` в некоторых средах } 6) Использовать методы массива (создаёт новую переменную в замыкании) Array.from({length: 3}).forEach((_, i) => { setTimeout(() => console.log(i), 100); }); Вывод: самый простой и современный способ — заменить `var` на `let`. Другие способы явно захватывают текущее значение итерации в отдельную переменную.
Пояснения по концепциям
- Замыкание: колбэк сохраняет ссылку на переменную iii, а не её моментальное значение. Значение берётся при выполнении колбэка (после цикла).
- Область видимости `var`: одна переменная iii для всего цикла (function-scoped), поэтому все итерации используют одинаковую переменную.
- Event loop / асинхронность: `setTimeout(..., 100)` ставит колбэк в очередь; основной цикл завершится раньше, чем таймеры сработают.
Пример проблемы
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Выведет: 3, 3, 3
Как исправить (несколько способов)
1) Использовать `let` (ES6, блоковая область)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Выведет: 0, 1, 2
2) Захватить значение через IIFE (устаревший способ)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// Выведет: 0, 1, 2
3) Создать замыкание через фабрику функций
function makeLogger(j) { return () => console.log(j); }
for (var i = 0; i < 3; i++) {
setTimeout(makeLogger(i), 100);
}
4) Использовать `bind`
for (var i = 0; i < 3; i++) {
setTimeout(console.log.bind(null, i), 100);
}
5) Передать аргументы в `setTimeout` (поддерживается в браузерах/Node)
for (var i = 0; i < 3; i++) {
setTimeout((v) => console.log(v), 100, i);
}
// или
for (var i = 0; i < 3; i++) {
setTimeout(console.log, 100, i); // осторожно с контекстом `this` в некоторых средах
}
6) Использовать методы массива (создаёт новую переменную в замыкании)
Array.from({length: 3}).forEach((_, i) => {
setTimeout(() => console.log(i), 100);
});
Вывод: самый простой и современный способ — заменить `var` на `let`. Другие способы явно захватывают текущее значение итерации в отдельную переменную.