Пытаясь понять сферу и замыкания - PullRequest
0 голосов
/ 02 июня 2018

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

Я понимаю первый блок, где вывод равен 5,5,5,5,5, потому что функция выполняется после завершения цикла for.Однако я не до конца понимаю, почему работает второй блок ... Правильно ли я считаю, что на каждой итерации вызывается новая функция, поэтому в памяти одновременно выполняется 5 функций?Я хотел бы простого для понимания объяснения, пожалуйста - я новичок в изучении JavaScript.

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log('index: ' + i);
  }, 1000);
}


for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

Ответы [ 3 ]

0 голосов
/ 02 июня 2018

Ваш пример 2, то есть:

for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

Работает нормально, потому что в этом примере вы организовываете для каждой функции времени ожидания отдельную копию «i», используя замыкания.

Вы даже можете использовать let для достижения этого, попробуйте следующее:

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log('index: ' + i);
    }, 1000);
}

Это работает, потому что в цикле с индексом на основе букв у каждой итерации цикла будет новое значение i, где каждое значение находится в пределах цикла, поэтомуваш код будет работать так, как вы ожидаете.

Для более подробного объяснения вы можете обратиться: Ссылка

0 голосов
/ 02 июня 2018

Рефакторинг моего ответа из-за ваших комментариев ниже.

Прежде чем мы начнем, нам нужно рассмотреть несколько терминов:

  1. Контекст выполнения - Проще говоря, это «среда», в которойФункция выполняется в. Например, когда наше приложение запускается, мы запускаем «глобальный» контекст выполнения, когда мы вызываем функцию, мы создаем новый контекст выполнения (вложенный в глобальный).
    Каждый контекст выполнения имеет переменную-окружение (область действия) и, конечно же, тело функции (это «команды»).

  2. Стек вызовов - Чтобы отслеживать, в каком контексте выполнения мы находимся, и какие переменные доступны для нас, каждый контекст выполнения передается в вызов.стек, когда функция возвращает ее извлеченную из стека вызовов, и ее среда помечается как сборщик мусора (освобождает память), за исключением одного исключения, которое мы выясним позже.

  3. API веб-браузера и цикл обработки событий - JavaScript является однопоточным (давайте назовем его потоком для простоты), но иногда нам нужно обрабатывать асинхронные действиятакие как click-events, xhr и timers.
    Браузер предоставляет их через свой API, addEventListener, XHR / fetch, setTimeout и т.д ...
    Самое классное то, что онбудет запускать его в другом потоке, чем поток JavaScript.Но как браузер может запустить наш код в главном потоке?через обратные вызовы, которые мы предоставляем ему (как вы сделали с setTimeout).
    Хорошо, когда он запустит наш код?нам нужен предсказуемый способ запуска нашего кода.
    Заходит в цикл обработки событий и в Callback-Que, браузер отправляет каждый обратный вызов в эту очередь (между прочим, обещания переходят в другую очередь с более высоким приоритетом) и цикл обработки событийнаблюдает за стеком вызовов, когда когда-либо стек вызовов пуст и больше нет кода для запуска в глобальном , цикл обработки событий захватит следующий обратный вызов и отправит его в стек вызовов.

  4. Закрытие - Проще говоря, это когда функция обращается к своей лексической (статической) области видимости, даже если она выходит за ее пределы.это будет яснее позже.

В примере # 1 - Мы запускаем цикл в глобальном контексте выполнения , создавая переменную i и меняя ее на новое значение на каждой итерации, одновременно передавая 5обратные вызовы в браузер (через setTimeout API).
Цикл обработки событий не может отправить эти обратные вызовы обратно в стек вызовов, поскольку он еще не пуст.однако, когда цикл завершился, стек вызовов пуст, и цикл обработки событий возвращает к нему наши обратные вызовы, каждый обратный вызов получает доступ к i и печатает последнее значение 5 (закрытие, мы получаем доступ к среде i послеон должен был быть уничтожен).причина этого в том, что все обратные вызовы были созданы в одном контексте выполнения, поэтому они ссылаются на один и тот же i.

В примере # 2 - Мы запускаем цикл в глобальном контексте выполнения , создавая новую функцию ( IIFE ) на каждой итерации,таким образом создавая новый контекст исполнения.это создаст копию i внутри этого контекста выполнения, а не в глобальном контексте, как раньше.Внутри этого контекста выполнения мы отправляем обратный вызов через setTimeout, как и прежде, чем цикл обработки событий ожидает, пока цикл не завершится, поэтому стек вызовов будет пустым и перенесет следующий обратный вызов в стек.но теперь, когда выполняется обратный вызов, он получает доступ к своему контексту выполнения, где он был создан, и печатает i, который никогда не изменялся глобальным контекстом.

Итак, в основном у нас есть 5 контекстов выполнения (без глобального), каждый из которых имеет свой i.

Надеюсь, теперь это стало понятнее.

Я очень рекомендую посмотреть это видео о цикле событий.

0 голосов
/ 02 июня 2018

Да, вы правы. 5 Функция будет выполнена, и нет необходимости в logIndex, вы можете использовать anonymous function для этого типа работы.

(funtion(index){}) => function определение

(funtion(index){})(i) => вызов function с передачей i.

for (var i = 0; i < 5; i++) {
  (function (index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}
...