for ( var i = 0; i < 5; i++ ) {
// Queue
setTimeout(function() {
console.log(i);
}, 0);
// i < 5; Execution stack ended
// Queue => execution происходит здесь? Не за телом цикла
console.log(i) // 5,5,5,5,5
}
//Queue => execution
i = 5
console.log(i) // 5
- 17
2 Answers
Для начала отметим, что область действия var — вся функция (или иное пространство имён). Можно считать, что при выполнении кода все var неявно перемещаются в начало:
var i;
for (i = 0; i < 5; i++ ) {}
Теперь про цикл for. Он состоит из трёх частей (не считая тела):
for (начало; условие; шаг) {тело}
В начале выполняется начало, потом проверяется условие; если оно истинно, выполняется тело цикла и затем выполняется шаг. Потом снова проверяется условие; если оно истинно, снова выполняется тело цикла и шаг; и так далее, пока условие не станет ложно.
Вот давайте так и разберём ваш цикл по шагам:
- начало: делаем
iравным 0 - проверяем условие:
0 < 5, условие истинно, выполняем тело цикла - выполняем шаг
i++; теперьiравно 1 - проверяем условие:
1 < 5, условие истинно, выполняем тело цикла - выполняем шаг
i++; теперьiравно 2 - проверяем условие:
2 < 5, условие истинно, выполняем тело цикла - выполняем шаг
i++; теперьiравно 3 - проверяем условие:
3 < 5, условие истинно, выполняем тело цикла - выполняем шаг
i++; теперьiравно 4 - проверяем условие:
4 < 5, условие истинно, выполняем тело цикла - выполняем шаг
i++; теперьiравно 5 - проверяем условие:
5 < 5— условие ложно! Ничего не выполняем, выходим из цикла - два шага назад
iстал равен 5, поэтомуconsole.log(i)выведет именно 5.
Функция в setTimeout даже с нулевой задержкой выполняется не сразу, а после того, как весь другой код завершился. В процессе выполнения цикла ваш код ещё не завершился (ваш код это работающий цикл, ага), поэтому во время выполнения вышеописанных шагов никакой setTimeout ещё не срабатывает.
Последняя строчка в вашем коде — console.log; после неё код завершается. И только тогда начинают запускаться все отложенные setTimeoutы! Но к этому времени цикл ведь уже выполнился и переменная i осталась равна 5 — именно поэтому вы в результате получите пять (или даже шесть с учётом последнего log) чисел 5 в консоли.
Чтобы этого избежать, можно использовать let, который, в отличие от var, действует только в пределах текущей итерации цикла и не существует за его пределами (работает только в новых браузерах):
console.log('Цикл начат');
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
console.log('Цикл завершён, сейчас будут setTimeoutы');
Или можно обернуть setTimeout в функцию и создать в ней новую переменную в виде аргумента функции, которая будет действовать только в пределах этой функции (работает и в старых браузерах):
console.log('Цикл начат');
for (var i = 0; i < 5; i++) {
(function(i_local) {
setTimeout(function() {
console.log(i_local);
}, 0);
})(i); // вызываем анонимную функцию, передавая i в аргументе
}
console.log('Цикл завершён с i = ' + i + ', сейчас пойдут setTimeoutы');
- 13,178
Наглядно разницу между var и let можно посмотреть на следующем примере:
console.log(a); //undefined
var a = 5;
console.log(a) //5
console.log(a); //Тут же выдаст ошибку a is not defined
let a = 5;
console.log(a);
Поэтому в циклах, как и пишут в комментариях, рекомендуется использовать let.
- 381
i++не станет выполняться, если это сделает условие ложным — вполне возможная ситуация для новичков, не до конца понимающих принципы, по которым выполняется код, я сам так же тупил восемь лет назад) – andreymal Nov 21 '17 at 13:04