Замыкания внутри циклов javascript

На jsgarden засек след. пример:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}


Данный код выводит 10 по 10 раз. Для меня данный результат абсолютно не очевиден. jsgarden аргументирует это тем, что:

Анонимная функция сохраняет лишь ссылку на i, и когда будет вызвана функция console.log, цикл for уже закончит свою работу — и поэтому в переменной i будет покоиться значение 10.


1. Если как они утверждают цикл будет закончен до того как будет вызвана console.log(i) то почему результат выводится 10 раз?
2. Как цикл может закончится до вызова console.log(i), если условие цикла является true 10 раз и такое же количество раз должен отрабатываться весь код внутри цикла?

Объясните пожалуйста подробнее как работает эта хитрая крупица кода.
  • Вопрос задан
  • 21984 просмотра
Решения вопроса 1
dizballanze
@dizballanze
Software developer at Yandex
1. Если как они утверждают цикл будет закончен до того как будет вызвана console.log(i) то почему результат выводится 10 раз?


У вас же timeout используется, т.е. 10 раз внутри цикла будет запущен таймер, пока первый из таймеров закончится цикл уже пройдет все 10 итераций и соотв i будет иметь значение 10.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 5
@constantant
for (var i = 0; i < 10; i++) { 
  setTimeout('console.log('+ i +')', 1000); 
}

feel_like_a_sir_yao_ming_by_dras0r-d5zk0
Ответ написан
aen
@aen
Keep calm and 'use strict';
1. 10 раз запустится отложенный console.log(i);, который лежит внутри анонимной функции, которая создается каждый раз.

Прочитайте статью habrahabr.ru/company/tradingview/blog/178261/. Она ответит на ваши вопросы.
Ответ написан
Комментировать
Opaspap
@Opaspap
Сначала цикл будет закончен, следовательно i выставлено в 10, а потом через 1 секунду вызовется 10 раз функция из замыкания в setTimeout (почти сразу, с разницей ~4мс), после того как последняя из этих функций выйдет область видимости, в которой находится цикл будет отдана на съедение уборщику мусора, до того все ссылки на переменные заданные в области видимости цикла будут считаться нужными.

Обход обычно делается примерно так:

for(var i = 0; i < 10; i++) {setTimeout(LogMe(i), //получим функцию
                                     1000*(i+1));} //видимо хочется не всё сразу через 1 секунду выполнить,
                                     // а по одному разу за 1 секунду (последний console.log() через 10 секунд)

function LogMe(i){ //т.к. i объявлена здесь (в аргументе это всё равно что объявление var), 
                            //то она больше не будет ссылкой на i из цикла
                           //эта области видимости отправится  к мусорщику сразу после выполнения следующей функции
return function() {     //вернем новую функцию со своей областью видимости.
        console.log(i);  
    };
 }


Всё вместе называется асинхронное выполнение.
Ответ написан
Комментировать
ali_aliev
@ali_aliev
Разработчик на Django/Python, JavaScript
Тут все дело в замыканиях :)
Что такое замыкания? Замыкания это функции, внутри которых создаются ссылки на внешние переменные ( то есть переменные, за пределами блока функции - замыкания ). Обратите внимания на слово ссылка, каждый раз как происходит итерация цикла, значения в i меняется тоже:

> var tmp = {};
undefined
> for(var i=0; i < 10; i++) {
... tmp[i] = function() { return i; };
... }
[Function]
> tmp[0]()
10
> tmp[1]()
10
> tmp[2]()
10
> ...
Ответ написан
@asArtem
for (var i = 0; i < 10; i++) { 
    setTimeout(function (i) // добавил аргумент i чтобы изменить скоуп и скрыть i  из цикла
    { 
      console.log(i); 
    }(i) // ---  добавил  ( i ) чтобы сразу вызвать функцию анонимную и сделать 
, 2220); 
  }


равносильно:

for (var i = 0; i < 10; i++) { 
    setTimeout(function ( k ) { 
      console.log( k ); 
    }( i ), 2220); 
  }
Ответ написан
Комментировать
Ваш ответ на вопрос

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

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