Дан код на JavaScript с асинхронными вызовами: async function f(){ for(var i=0;iconsole.log(i),100); } } f(); Объясните результат, предложите несколько корректных реализаций, и обсудите влияние лексической области видимости
Объяснение результата - В вашем коде переменная объявлена как `var i`. `var` — функциональная (не блочная) область видимости, поэтому внутри функции `f` существует одна общая переменная `i`, которая инкрементируется циклом до значения 333. setTimeout создаёт отложенные колбэки, которые выполнятся после завершения синхронного цикла. К тому моменту `i === 333`, и все три колбэка прочитают это же значение. В итоге в консоль попадут три строки с 3,3,33, 3, 33,3,3. Краткий вывод: вывод будет 3,3,33, 3, 33,3,3. Несколько корректных реализаций 1) Использовать `let` в заголовке цикла (ES6) — для каждой итерации создаётся своя связка переменной: ```js async function f(){ for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } } f(); // Вывод: 0, 1, 2 ``` 2) Захват значения через IIFE (замыкание, старый стиль): ```js async function f(){ for (var i = 0; i < 3; i++) { (function(j){ setTimeout(() => console.log(j), 100); })(i); } } f(); // Вывод: 0, 1, 2 ``` 3) Передать аргумент в setTimeout (в отдельных средах/браузерах поддерживается): ```js async function f(){ for (var i = 0; i < 3; i++) { setTimeout((x) => console.log(x), 100, i); } } f(); // Вывод: 0, 1, 2 ``` 4) Использовать функцию-обёртку при вызове: ```js async function f(){ for (var i = 0; i < 3; i++) { const j = i; setTimeout(() => console.log(j), 100); } } f(); // Вывод: 0, 1, 2 ``` 5) Если нужно последовательное ожидание между итерациями (меняет семантику): ```js async function f(){ for (let i = 0; i < 3; i++) { await new Promise(r => setTimeout(r, 100)); console.log(i); } } f(); // Вывод (с паузами): 0, 1, 2 ``` Влияние лексической области видимости (block vs function scope) - `var` имеет функциональную область видимости: одна переменная `i` для всего цикла; замыкания внутри цикла будут ссылаться на одну и ту же переменную (поэтому всё выводят текущее/окончательное значение). - `let` и `const` имеют блочную область видимости и в конструкциях `for` спецификация ES6 создаёт новую привязку переменной для каждой итерации. Замыкание, созданное на этой итерации, захватывает именно эту привязку, поэтому получает текущее значение итерации. - Итого: при работе с асинхронными колбэками в цикле обычно предпочтительнее `let` или явный захват значения (IIFE/копия) — это даёт ожидаемое поведение. Дополнительно: `async` в объявлении функции сам по себе тут ни на что не влияет, если вы не используете `await` внутри — цикл остаётся синхронным, колбэки выполнятся позже.
- В вашем коде переменная объявлена как `var i`. `var` — функциональная (не блочная) область видимости, поэтому внутри функции `f` существует одна общая переменная `i`, которая инкрементируется циклом до значения 333. setTimeout создаёт отложенные колбэки, которые выполнятся после завершения синхронного цикла. К тому моменту `i === 333`, и все три колбэка прочитают это же значение. В итоге в консоль попадут три строки с 3,3,33, 3, 33,3,3.
Краткий вывод: вывод будет 3,3,33, 3, 33,3,3.
Несколько корректных реализаций
1) Использовать `let` в заголовке цикла (ES6) — для каждой итерации создаётся своя связка переменной:
```js
async function f(){
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
}
f();
// Вывод: 0, 1, 2
```
2) Захват значения через IIFE (замыкание, старый стиль):
```js
async function f(){
for (var i = 0; i < 3; i++) {
(function(j){
setTimeout(() => console.log(j), 100);
})(i);
}
}
f();
// Вывод: 0, 1, 2
```
3) Передать аргумент в setTimeout (в отдельных средах/браузерах поддерживается):
```js
async function f(){
for (var i = 0; i < 3; i++) {
setTimeout((x) => console.log(x), 100, i);
}
}
f();
// Вывод: 0, 1, 2
```
4) Использовать функцию-обёртку при вызове:
```js
async function f(){
for (var i = 0; i < 3; i++) {
const j = i;
setTimeout(() => console.log(j), 100);
}
}
f();
// Вывод: 0, 1, 2
```
5) Если нужно последовательное ожидание между итерациями (меняет семантику):
```js
async function f(){
for (let i = 0; i < 3; i++) {
await new Promise(r => setTimeout(r, 100));
console.log(i);
}
}
f();
// Вывод (с паузами): 0, 1, 2
```
Влияние лексической области видимости (block vs function scope)
- `var` имеет функциональную область видимости: одна переменная `i` для всего цикла; замыкания внутри цикла будут ссылаться на одну и ту же переменную (поэтому всё выводят текущее/окончательное значение).
- `let` и `const` имеют блочную область видимости и в конструкциях `for` спецификация ES6 создаёт новую привязку переменной для каждой итерации. Замыкание, созданное на этой итерации, захватывает именно эту привязку, поэтому получает текущее значение итерации.
- Итого: при работе с асинхронными колбэками в цикле обычно предпочтительнее `let` или явный захват значения (IIFE/копия) — это даёт ожидаемое поведение.
Дополнительно: `async` в объявлении функции сам по себе тут ни на что не влияет, если вы не используете `await` внутри — цикл остаётся синхронным, колбэки выполнятся позже.