for-, if-, switch-блоки не создают контекста.
arr.push(() => {
return Promise.resolve(item); // Все промисы вернут C
});
В функции переменная item меняется и после цикла становится равна своему конечному значению. Вот замыкание и ссылается на это значение.
А если обернуть это в 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);
}
Ну а в третьем примере используется const, а в es2015 она живёт внутри блока, так что там всё ок.
1) Это все синхронные операции, т.е. Эвент машина JavaScript'а должна выполнить их в одной итерации, но при этом for ведет себя как асинхронная операция, т.е. сначала перебирает все варианты, а потом уже пушит их.
нет, просто если не оборачивать в iife, то все замыкания связывается с одной и той же переменной.
2) Какие из операций JS являются асинхронными?
Все I/O-функции, ну и что-то ещё, типа таймаутов и др. for, if, etc - операторы, они не асинхронные по своей сути.
3) В каких случаях нужно использовать замыкания помимо циклов?
Для инкапсуляции, например. Вообще, замыкания так или иначе используются почти всегда.
4) Почему если объявить переменную в цикле, она ведет себя как будто она в замыкании, ведь на самом деле итерируется не она сама, а i?
Смотря как объявить. Если по-старинке, то область видимости у неё будет равна области видимости внешней функции. Если же let/const, то у них область видимости ограничена блоком, т.е. замыкания в цикле будут ссылаться на разные переменные.