Найдите и объясните проблему в этом фрагменте JavaScript: for (var i=0;iconsole.log(i),100); } — почему он печатает не 0..4, как это исправить в ES5 и ES6+, и какие концепции языка здесь задействованы (замыкания, область видимости переменных)?
Проблема: переменная `i` объявлена как `var`, то есть функция-область. Все колбэки в `setTimeout` захватывают одну и ту же переменную `i`, и к моменту выполнения таймаутов цикл уже завершён и `i = 555`. Поэтому выводится не `000`…`444`, а пять раз значение `555`. Почему так происходит (кратко): замыкание + лексическая область видимости `var` — замыкание сохраняет ссылку на переменную, а не её текущую величину. Как исправить. ES5 (без `let`): - IIFE (создать новую локальную переменную для каждой итерации): `for (var i = 0; i < 555; i++) { (function(j){ setTimeout(function(){ console.log(j); }, 100100100); })(i); }` - Фабрика функций: `function makeLogger(j){ return function(){ console.log(j); }; }` `for (var i = 0; i < 555; i++) { setTimeout(makeLogger(i), 100100100); }` - Или привязать аргумент через `bind`: `for (var i = 0; i < 555; i++) { setTimeout(console.log.bind(null, i), 100100100); }` ES6+ (проще): - Использовать `let` в заголовке цикла — для `let` создаётся новая привязка переменной на каждой итерации: `for (let i = 0; i < 555; i++) { setTimeout(()=>console.log(i), 100100100); }` Задействованные концепции языка: - Замыкания (closures): колбэк захватывает переменную из внешней области. - Область видимости: `var` — функциональная/скопированная, `let/const` — блочная и имеют per-iteration binding в `for`. - Hoisting/TDZ (коротко): `var` поднимается и существует до выполнения, `let`/`const` имеют временную мёртвую зону до объявления, что влияет на поведение и безопасность.
Почему так происходит (кратко): замыкание + лексическая область видимости `var` — замыкание сохраняет ссылку на переменную, а не её текущую величину.
Как исправить.
ES5 (без `let`):
- IIFE (создать новую локальную переменную для каждой итерации):
`for (var i = 0; i < 555; i++) { (function(j){ setTimeout(function(){ console.log(j); }, 100100100); })(i); }`
- Фабрика функций:
`function makeLogger(j){ return function(){ console.log(j); }; }`
`for (var i = 0; i < 555; i++) { setTimeout(makeLogger(i), 100100100); }`
- Или привязать аргумент через `bind`:
`for (var i = 0; i < 555; i++) { setTimeout(console.log.bind(null, i), 100100100); }`
ES6+ (проще):
- Использовать `let` в заголовке цикла — для `let` создаётся новая привязка переменной на каждой итерации:
`for (let i = 0; i < 555; i++) { setTimeout(()=>console.log(i), 100100100); }`
Задействованные концепции языка:
- Замыкания (closures): колбэк захватывает переменную из внешней области.
- Область видимости: `var` — функциональная/скопированная, `let/const` — блочная и имеют per-iteration binding в `for`.
- Hoisting/TDZ (коротко): `var` поднимается и существует до выполнения, `let`/`const` имеют временную мёртвую зону до объявления, что влияет на поведение и безопасность.