HoHsi
@HoHsi

Почему итератор цикле нужно передавать в замыкание?

Добрый день!
Я не первый день пишу на JS, но я до сих пор не могу понять что не так с for в JS. Допустим:
'use strict';

const arrOfItems = [
    'a',
    'b',
    'c'
];

let arr = [];

let item;
for (let i = 0, len = 3; i < len; i++) {
    item = arrOfItems[i];

    arr.push(() => {
        return Promise.resolve(item); // Все промисы вернут C
    });
}

arr = arr.map(item => item());

Promise.all(arr)
.then((results) => {
    console.log(results);
});


но

'use strict';

const arrOfItems = [
    'a',
    'b',
    'c'
];

let arr = [];

let item;
for (let i = 0, len = 3; i < len; i++) {
    ((item) => {
        item = arrOfItems[i];

        arr.push(() => {
            return Promise.resolve(item);
        });
    })(item);
}

arr = arr.map(item => item());

Promise.all(arr)
.then((results) => {
    console.log(results);
});


или же

'use strict';

const arrOfItems = [
    'a',
    'b',
    'c'
];

let arr = [];

for (let i = 0, len = 3; i < len; i++) {
    const item = arrOfItems[i];

    arr.push(() => {
        return Promise.resolve(item);
    });
}

arr = arr.map(item => item());

Promise.all(arr)
.then((results) => {
    console.log(results);
});


Возвращают все как надо, т.е. ['a', 'b', 'c']. Я знаю, что такое IIFE, и что такое лексическая область видимости, но я не понимат:
1) Это все синхронные операции, т.е. Эвент машина JavaScript'а должна выполнить их в одной итерации, но при этом for ведет себя как асинхронная операция, т.е. сначала перебирает все варианты, а потом уже пушит их.

2) Какие из операций JS являются асинхронными?

3) В каких случаях нужно использовать замыкания помимо циклов?

4) Почему если объявить переменную в цикле, она ведет себя как будто она в замыкании, ведь на самом деле итерируется не она сама, а i?
  • Вопрос задан
  • 368 просмотров
Решения вопроса 1
@bromzh
Drugs-driven development
for-, if-, switch-блоки не создают контекста.
arr.push(() => {
        return Promise.resolve(item); // Все промисы вернут C
    });

В функции переменная item меняется и после цикла становится равна своему конечному значению. Вот замыкание и ссылается на это значение. 775f8192fca64f7dba3d26d66086402c.png

А если обернуть это в iife, то на каждой итерации создаётся локальный контекст, в котором создаётся замыкание, которое, в свою очередь, будет ссылаться на локальную переменную item.

PS да, во втором примере не совсем логично сделано, лучше делать не так:
let item;
for (let i = 0, len = 3; i < len; i++) {
    ((item) => {
        item = arrOfItems[i];

        arr.push(() => {
            return Promise.resolve(item);
        });
    })(item);
}

а
for (let i = 0, len = 3; i < len; i++) {
    ((idx) => { // это для наглядности, её тоже можно назвать i и тогда она "затенит" внешнюю i
        let item = arrOfItems[idx];
        arr.push(() => {
            return Promise.resolve(item);
        });
    })(i);
}

451fd34a43824ccb8a3c9d79f9b7e481.png

Ну а в третьем примере используется const, а в es2015 она живёт внутри блока, так что там всё ок.

1) Это все синхронные операции, т.е. Эвент машина JavaScript'а должна выполнить их в одной итерации, но при этом for ведет себя как асинхронная операция, т.е. сначала перебирает все варианты, а потом уже пушит их.

нет, просто если не оборачивать в iife, то все замыкания связывается с одной и той же переменной.

2) Какие из операций JS являются асинхронными?

Все I/O-функции, ну и что-то ещё, типа таймаутов и др. for, if, etc - операторы, они не асинхронные по своей сути.

3) В каких случаях нужно использовать замыкания помимо циклов?

Для инкапсуляции, например. Вообще, замыкания так или иначе используются почти всегда.

4) Почему если объявить переменную в цикле, она ведет себя как будто она в замыкании, ведь на самом деле итерируется не она сама, а i?

Смотря как объявить. Если по-старинке, то область видимости у неё будет равна области видимости внешней функции. Если же let/const, то у них область видимости ограничена блоком, т.е. замыкания в цикле будут ссылаться на разные переменные.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
В первом примере объявление item перенесите в цикл. У let и const - область видимости - блок {}
for (let i = 0, len = 3; i < len; i++) {
    let item = arrOfItems[i];
    arr.push(() => {
        return Promise.resolve(item);
    });
}

Второй пример - обычный танец с бубном до появления ES6, но вы же тут фигачите по-новому).

Третий пример аналогичен моему первому

Можно еще вспомнить, что лямбда выражения сохраняют контекст, и тогда
for (let i = 0, len = 3; i < len; i++) {
    arr.push(() => {
        return Promise.resolve(arrOfItems[i]); 
    });
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы