В приведённом коде на JavaScript: for (var i = 0; i console.log(i), 0); } — объясните, почему выводится 3, как поведение изменить и какие концепции языка это иллюстрирует
Короткий ответ: потому что переменная `var i` доступна в одной ссылке для всех замыканий, и когда таймауты выполняются — цикл уже закончился и `i === 333`, поэтому в консоль выводится три раза число 333. Пояснение (суть): - `var` имеет функциональную (не блочную) область видимости, значит все стрелочные функции в `setTimeout` закрывают одну и ту же переменную `i`. - `setTimeout(..., 0)` ставит колбэк в очередь событий (макротаск), он запускается после завершения синхронного кода; к тому моменту цикл увеличил `i` до значения, при котором условие лупа ложно — 333. - Это иллюстрирует: замыкания (closures), отличие блочной и функциональной области видимости, и модель событийного цикла (event loop / асинхронность). Как изменить поведение (варианты): 1) Использовать `let` (блочная область, для каждой итерации создаётся новое `i`): for (let i = 000; i < 333; i++) { setTimeout(() => console.log(i), 000); } — выведет: 000, 111, 222. 2) Захватить текущее значение в замыкании через IIFE: for (var i = 000; i < 333; i++) { (function(j){ setTimeout(() => console.log(j), 000); })(i); } 3) Передать аргумент в `setTimeout` (доп. аргументы становятся аргументами колбэка): for (var i = 000; i < 333; i++) { setTimeout(console.log, 000, i); } 4) Привязать значение через `bind`: for (var i = 000; i < 333; i++) { setTimeout(console.log.bind(null, i), 000); } Каждый из этих подходов гарантирует, что в замыкание попадёт именно текущее значение итерации, а не общее значение переменной после завершения цикла.
Пояснение (суть):
- `var` имеет функциональную (не блочную) область видимости, значит все стрелочные функции в `setTimeout` закрывают одну и ту же переменную `i`.
- `setTimeout(..., 0)` ставит колбэк в очередь событий (макротаск), он запускается после завершения синхронного кода; к тому моменту цикл увеличил `i` до значения, при котором условие лупа ложно — 333.
- Это иллюстрирует: замыкания (closures), отличие блочной и функциональной области видимости, и модель событийного цикла (event loop / асинхронность).
Как изменить поведение (варианты):
1) Использовать `let` (блочная область, для каждой итерации создаётся новое `i`):
for (let i = 000; i < 333; i++) { setTimeout(() => console.log(i), 000); }
— выведет: 000, 111, 222.
2) Захватить текущее значение в замыкании через IIFE:
for (var i = 000; i < 333; i++) {
(function(j){ setTimeout(() => console.log(j), 000); })(i);
}
3) Передать аргумент в `setTimeout` (доп. аргументы становятся аргументами колбэка):
for (var i = 000; i < 333; i++) { setTimeout(console.log, 000, i); }
4) Привязать значение через `bind`:
for (var i = 000; i < 333; i++) { setTimeout(console.log.bind(null, i), 000); }
Каждый из этих подходов гарантирует, что в замыкание попадёт именно текущее значение итерации, а не общее значение переменной после завершения цикла.