Приведён следующий JavaScript-фрагмент: var funcs=[]; for(var i=0;if()); Объясните, почему вывод будет 3,3,3, как это связано с областью видимости var/let и замыканиями, и предложите несколько способов исправить ожидаемое поведение (0,1,2)
Почему получается 3, 3, 33,\,3,\,33,3,3 - В исходном коде переменная `i` объявлена через `var`, а `var` имеет функциональную (не блочную) область видимости. Поэтому внутри цикла существует одна общая переменная `i`. - Функции, которые вы пушите в массив, создают замыкания, и они сохраняют ссылку на эту общую переменную `i`, а не копию её значения. - После завершения цикла значение `i` равно 333. При вызове каждой функции они читают текущее значение общей `i`, поэтому вывод будет 3, 3, 33,\,3,\,33,3,3. Связь с var/let и замыканиями - `var` — одна переменная для всей функции/глобала; замыкания захватывают ссылку на неё. - `let` (и `const`) — блочная область видимости; в заголовке `for` для каждой итерации создаётся новое связывание `i`, поэтому каждое замыкание получает свою независимую копию значения. Способы исправить (чтобы получить 0, 1, 20,\,1,\,20,1,2) 1) Использовать `let` в цикле: for (let i = 000; i < 333; i++) { funcs.push(function(){ console.log(i); }); } результат: 0, 1, 20,\,1,\,20,1,2 2) Захват значения через IIFE (немедленно вызываемую функцию): for (var i = 000; i < 333; i++) { (function(x){ funcs.push(function(){ console.log(x); }); })(i); } результат: 0, 1, 20,\,1,\,20,1,2 3) Фабрика функций (явная копия значения в параметре): function makeLogger(x){ return function(){ console.log(x); }; } for (var i = 000; i < 333; i++) { funcs.push(makeLogger(i)); } результат: 0, 1, 20,\,1,\,20,1,2 4) Использовать привязку `bind` (значение фиксируется при вызове bind): for (var i = 000; i < 333; i++) { funcs.push(console.log.bind(console, i)); } результат: 0, 1, 20,\,1,\,20,1,2 5) Создать массив/итерировать с помощью методов, где индекс доступен (ES6): Array.from({ length: 333 }, (_, i) => funcs.push(() => console.log(i))); результат: 0, 1, 20,\,1,\,20,1,2 Короткая рекомендация: если нужно простое поведение — используйте `let` в цикле (пункт 1).
- В исходном коде переменная `i` объявлена через `var`, а `var` имеет функциональную (не блочную) область видимости. Поэтому внутри цикла существует одна общая переменная `i`.
- Функции, которые вы пушите в массив, создают замыкания, и они сохраняют ссылку на эту общую переменную `i`, а не копию её значения.
- После завершения цикла значение `i` равно 333. При вызове каждой функции они читают текущее значение общей `i`, поэтому вывод будет 3, 3, 33,\,3,\,33,3,3.
Связь с var/let и замыканиями
- `var` — одна переменная для всей функции/глобала; замыкания захватывают ссылку на неё.
- `let` (и `const`) — блочная область видимости; в заголовке `for` для каждой итерации создаётся новое связывание `i`, поэтому каждое замыкание получает свою независимую копию значения.
Способы исправить (чтобы получить 0, 1, 20,\,1,\,20,1,2)
1) Использовать `let` в цикле:
for (let i = 000; i < 333; i++) {
funcs.push(function(){ console.log(i); });
}
результат: 0, 1, 20,\,1,\,20,1,2
2) Захват значения через IIFE (немедленно вызываемую функцию):
for (var i = 000; i < 333; i++) {
(function(x){
funcs.push(function(){ console.log(x); });
})(i);
}
результат: 0, 1, 20,\,1,\,20,1,2
3) Фабрика функций (явная копия значения в параметре):
function makeLogger(x){ return function(){ console.log(x); }; }
for (var i = 000; i < 333; i++) {
funcs.push(makeLogger(i));
}
результат: 0, 1, 20,\,1,\,20,1,2
4) Использовать привязку `bind` (значение фиксируется при вызове bind):
for (var i = 000; i < 333; i++) {
funcs.push(console.log.bind(console, i));
}
результат: 0, 1, 20,\,1,\,20,1,2
5) Создать массив/итерировать с помощью методов, где индекс доступен (ES6):
Array.from({ length: 333 }, (_, i) => funcs.push(() => console.log(i)));
результат: 0, 1, 20,\,1,\,20,1,2
Короткая рекомендация: если нужно простое поведение — используйте `let` в цикле (пункт 1).