Закрытие JavaScript внутри циклов - простой практический пример

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

Это выводит это:

Моя ценность: 3
Моя ценность: 3
Моя ценность: 3

Принимая во внимание, что я хотел бы это вывести:

Мое значение: 0
Мое значение: 1
Мое значение: 2


Та же проблема возникает, когда задержка запуска функции вызвана использованием прослушивателей событий:

var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

... или асинхронный код, например, используя Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

Каково решение этой основной проблемы?

45 ответов

Решение

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

Классическое решение: затворы

Что вы хотите сделать, это связать переменную в каждой функции с отдельным неизменным значением вне функции:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Поскольку в JavaScript отсутствует область блока - только область функции - оборачивая создание функции в новую функцию, вы гарантируете, что значение "i" останется таким, как вы предполагали.


Решение 2015: forEach

При относительно широкой доступности Array.prototype.forEach функция (в 2015 году), стоит отметить, что в тех ситуациях, когда итерация происходит в основном по массиву значений, .forEach() обеспечивает чистый, естественный способ получить четкое замыкание для каждой итерации. То есть, если у вас есть какой-то массив, содержащий значения (ссылки DOM, объекты и т. Д.), И возникает проблема настройки обратных вызовов, специфичных для каждого элемента, вы можете сделать это:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

Идея состоит в том, что каждый вызов функции обратного вызова, используемой с .forEach цикл будет собственным замыканием. Параметр, передаваемый этому обработчику, является элементом массива, специфичным для данного конкретного шага итерации. Если он используется в асинхронном обратном вызове, он не будет вступать в конфликт с любыми другими обратными вызовами, установленными на других этапах итерации.

Если вы работаете в JQuery, то $.each() Функция дает вам аналогичные возможности.


Решение ES6: let

ECMAScript 6 (ES6) представляет новый let а также const ключевые слова, которые имеют другую область видимости, чем var переменные Например, в цикле с let на основе индекса, каждая итерация цикла будет иметь новое значение i где каждое значение находится внутри цикла, поэтому ваш код будет работать так, как вы ожидаете. Ресурсов много, но я бы рекомендовал пост-обзор 2ality в качестве отличного источника информации.

for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

Однако помните, что IE9-IE11 и Edge до Edge 14 поддерживают let но поймите вышесказанное неправильно (они не создают новый i каждый раз, так что все функции выше будут регистрировать 3, как если бы мы использовали var). Край 14, наконец, понимает это правильно.

Пытаться:

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Изменить (2014):

Лично я думаю, что более свежий ответ @Aust об использовании .bind это лучший способ сделать это сейчас. Там также lo-тире / подчеркивание _.partial когда вам не нужно или вы хотите возиться bind "s thisArg,

Еще один способ, который еще не был упомянут, это использование Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

ОБНОВИТЬ

Как отмечают @squint и @mekdev, вы получаете лучшую производительность, создавая функцию вне цикла, а затем связывая результаты внутри цикла.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

Использование выражения " Немедленно вызванная функция"- самый простой и читаемый способ заключить индексную переменную:

for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}

Это отправляет итератор i в анонимную функцию, которую мы определяем как index, Это создает замыкание, где переменная i сохраняется для последующего использования в любых асинхронных функциях IIFE.

Немного опоздал на вечеринку, но я изучал эту проблему сегодня и заметил, что многие из ответов не полностью касаются того, как Javascript обрабатывает области видимости, что, по сути, сводится к этому.

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

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Как и прежде, где каждая внутренняя функция выводит последнее значение, назначенное i теперь каждая внутренняя функция просто выводит последнее значение, присвоенное ilocal, Но не должна ли каждая итерация иметь свою ilocal?

Оказывается, это проблема. Каждая итерация использует одну и ту же область видимости, поэтому каждая итерация после первой просто перезаписывается ilocal, От MDN:

Важный: у JavaScript нет блока блока Переменные, введенные с блоком, попадают в область действия содержащей их функции или сценария, и последствия их установки сохраняются за пределами самого блока. Другими словами, операторы блока не вводят область действия. Хотя "автономные" блоки являются допустимым синтаксисом, вы не хотите использовать автономные блоки в JavaScript, потому что они не делают то, что вы думаете, они делают, если вы думаете, что они делают что-то вроде таких блоков в C или Java.

Подтверждено для акцента:

У JavaScript нет блочной области видимости. Переменные, введенные с блоком, попадают в область действия содержащей их функции или скрипта

Мы можем увидеть это, проверив ilocal прежде чем мы объявим это в каждой итерации:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

Именно поэтому этот баг такой хитрый. Даже если вы переделываете переменную, Javascript не выдаст ошибку, а JSLint даже не выдаст предупреждение. Вот почему лучший способ решить эту проблему - воспользоваться преимуществами замыканий, что по сути состоит в том, что в Javascript внутренние функции имеют доступ к внешним переменным, поскольку внутренние области "заключают" внешние области.

Затворы

Это также означает, что внутренние функции "держат" внешние переменные и поддерживают их, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-обертку исключительно для создания новой области, объявляем ilocal в новой области и вернуть внутреннюю функцию, которая использует ilocal (более подробное объяснение ниже):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Создание внутренней функции внутри функции-оболочки дает внутренней функции частную среду, к которой только она может получить доступ, "замыкание". Таким образом, каждый раз, когда мы вызываем функцию-обертку, мы создаем новую внутреннюю функцию со своей отдельной средой, гарантируя, что ilocal переменные не сталкиваются и не перезаписывают друг друга. Несколько небольших оптимизаций дают окончательный ответ, который дали многие другие пользователи SO:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

Обновить

С ES6 теперь основной, теперь мы можем использовать новый let Ключевое слово для создания блочных переменных:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

Посмотри, как это просто сейчас! Для получения дополнительной информации см. Этот ответ, на котором основана моя информация.

С ES6, теперь широко поддерживаемой, лучший ответ на этот вопрос изменился. ES6 обеспечивает let а также const ключевые слова для этого точного обстоятельства. Вместо того, чтобы возиться с замыканиями, мы можем просто использовать let установить переменную области видимости цикла следующим образом:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val затем будет указывать на объект, который является специфическим для этого конкретного поворота цикла, и будет возвращать правильное значение без дополнительной записи замыкания. Это, очевидно, значительно упрощает эту проблему.

const похож на let с дополнительным ограничением, что имя переменной не может быть восстановлено до новой ссылки после первоначального присваивания.

Поддержка браузеров теперь доступна для тех, кто ориентирован на последние версии браузеров. const/let В настоящее время поддерживаются последние версии Firefox, Safari, Edge и Chrome. Он также поддерживается в Node, и вы можете использовать его где угодно, используя такие инструменты сборки, как Babel. Вы можете увидеть рабочий пример здесь: http://jsfiddle.net/ben336/rbU4t/2/

Документы здесь:

Остерегайтесь, однако, что IE9-IE11 и Edge до Edge 14 поддерживают let но поймите вышесказанное неправильно (они не создают новый i каждый раз, так что все функции выше будут регистрировать 3, как если бы мы использовали var). Край 14, наконец, понимает это правильно.

Еще один способ сказать, что i в вашей функции связано время выполнения функции, а не время создания функции.

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

Большинство других ответов предоставляют способы обхода путем создания другой переменной, которая не изменит значения для вас.

Просто подумал, что добавлю объяснение для ясности. Для решения лично я бы пошел с Харто, так как это самый очевидный способ сделать это из ответов здесь. Любой из опубликованного кода будет работать, но я бы предпочел фабрику замыканий, а не писать кучу комментариев, чтобы объяснить, почему я объявляю новую переменную (Фредди и 1800-е) или у меня странный встроенный синтаксис замыкания (apphacker).

Что вам нужно понять, так это то, что область действия переменных в javascript основана на функции. Это важное отличие, чем, скажем, C#, где у вас есть область видимости блока, и просто скопировать переменную в одну из for будет работать.

Завершение этого в функцию, которая оценивает возвращение функции, как ответ apphacker, сделает свое дело, поскольку переменная теперь имеет область действия функции.

Существует также ключевое слово let вместо var, которое позволит использовать правило области видимости блока. В этом случае определение переменной внутри for сделает свое дело. Тем не менее, ключевое слово let не является практическим решением из-за совместимости.

var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}

Вот еще один вариант в технике, похожей на метод Бьорна (apphacker), который позволяет назначать значение переменной внутри функции, а не передавать ее в качестве параметра, что иногда может быть более понятным:

for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Обратите внимание, что какой бы метод вы ни использовали, index переменная становится своего рода статической переменной, привязанной к возвращенной копии внутренней функции. Т.е. изменения его значения сохраняются между вызовами. Это может быть очень удобно.

Это описывает распространенную ошибку с использованием замыканий в JavaScript.

Функция определяет новую среду

Рассматривать:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

За каждый раз makeCounter вызывается, {counter: 0} приводит к созданию нового объекта. Также новая копия obj также создается для ссылки на новый объект. Таким образом, counter1 а также counter2 независимы друг от друга.

Замыкания в петлях

Использовать замыкание в цикле сложно.

Рассматривать:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Заметить, что counters[0] а также counters[1] не являются независимыми. На самом деле они работают на том же obj!

Это потому, что есть только одна копия obj распределяется между всеми итерациями цикла, возможно, по соображениям производительности. Даже если {counter: 0} создает новый объект в каждой итерации, ту же копию obj будет просто обновляться со ссылкой на новейший объект.

Решение состоит в том, чтобы использовать другую вспомогательную функцию:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

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

Для подробного обсуждения, пожалуйста, посмотрите ошибки и проблемы закрытия JavaScript

Самое простое решение было бы,

Вместо того, чтобы использовать:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

который предупреждает "2", 3 раза. Это связано с тем, что анонимные функции, созданные в цикле for, имеют одно и то же замыкание, а в этом замыкании значение i та же. Используйте это для предотвращения общего закрытия:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

Идея, лежащая в основе этого, заключается в том, чтобы инкапсулировать все тело цикла for с помощью IIFE -выражения (немедленного вызова функции) и передать new_i в качестве параметра и захвата его как i, Поскольку анонимная функция выполняется немедленно, i значение отличается для каждой функции, определенной внутри анонимной функции.

Похоже, что это решение подходит для любой такой проблемы, поскольку оно потребует минимальных изменений исходного кода, страдающего от этой проблемы. На самом деле, это по замыслу, это не должно быть проблемой вообще!

Вот простое решение, которое использует forEach (работает обратно в IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Печать:

My value: 0
My value: 1
My value: 2

попробуйте этот более короткий

  • нет массива

  • нет лишних для цикла


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/

Основная проблема с кодом, показанным OP, заключается в том, что i никогда не читается до второго цикла. Чтобы продемонстрировать, представьте, что видите ошибку внутри кода

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

Ошибка на самом деле не происходит, пока funcs[someIndex] выполняется (), Используя ту же логику, должно быть очевидно, что значение i также не собирается до этого момента. Как только исходный цикл заканчивается, i++ приносит i к стоимости 3 что приводит к состоянию i < 3 сбой и окончание цикла. С этой точки зрения, i является 3 и так когда funcs[someIndex]() используется, и i оценивается, это 3 - каждый раз.

Чтобы пройти это, вы должны оценить i как это встречается. Обратите внимание, что это уже произошло в форме funcs[i] (где есть 3 уникальных индекса). Есть несколько способов получить это значение. Одним из них является передача его в качестве параметра функции, которая показана здесь несколькими способами.

Другой вариант - создать функциональный объект, который сможет закрывать переменную. Это может быть достигнуто таким образом

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

Функции JavaScript "закрывают" область, к которой они имеют доступ при объявлении, и сохраняют доступ к этой области, даже если переменные в этой области изменяются.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

Каждая функция в приведенном выше массиве закрывается над глобальной областью действия (глобальной, просто потому, что это та область, в которой они объявлены).

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

"Функции JavaScript закрывают область, в которой они объявлены, и сохраняют доступ к этой области даже при изменении значений переменных внутри этой области".

С помощью let вместо var решает это путем создания новой области видимости каждый раз, когда for цикл запускается, создавая отдельную область видимости для каждой закрываемой функции. Различные другие методы делают то же самое с дополнительными функциями.

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

(let создает переменные, которые имеют область видимости блока вместо области видимости функции. Блоки обозначаются фигурными скобками, но в случае цикла for переменная инициализации i в нашем случае считается объявленным в фигурных скобках.)

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

  • Каждое определение функции образует область, состоящую из всех локальных переменных, объявленных var И его arguments,
  • Если у нас есть внутренняя функция, определенная внутри другой (внешней) функции, это формирует цепочку и будет использоваться во время выполнения
  • Когда функция выполняется, среда выполнения оценивает переменные путем поиска в цепочке областей действия. Если переменная может быть найдена в определенной точке цепочки, она прекращает поиск и использует ее, в противном случае она продолжается до достижения глобальной области видимости, которая принадлежит window,

В исходном коде:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

когда funcs будет выполнена, цепочка областей действия будет function inner -> global, Поскольку переменная i не может быть найден в function inner (ни один не объявлен с использованием var и не передаются в качестве аргументов), он продолжает поиск, пока значение i в конечном итоге находится в глобальной области видимости, которая window.i,

Обернув его во внешнюю функцию, либо явно определите вспомогательную функцию, как это сделал harto, либо используйте анонимную функцию, как это сделал Бьорн:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

когда funcs выполняется, теперь цепочка областей действия будет function inner -> function outer, Этот раз i можно найти в области видимости внешней функции, которая выполняется 3 раза в цикле for, каждый раз имеет значение i связаны правильно. Это не будет использовать значение window.i когда внутреннее исполнение.

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

С новыми функциями ES6 можно управлять областями на уровне блоков:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Код в вопросе OP заменен на let вместо var,

Мы проверим, что на самом деле происходит, когда вы объявляете var а также let по одному.

Случай 1: использованиеvar

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

Теперь откройте окно консоли Chrome, нажав клавишу F12, и обновите страницу. Расходуйте каждые 3 функции внутри массива. Вы увидите свойство с именем [[Scopes]].Разверните это. Вы увидите один объект массива с именем "Global"Раскройте это. Вы найдете недвижимость 'i' объявлен в объект, имеющий значение 3.

Заключение:

  1. Когда вы объявляете переменную, используя 'var' вне функции она становится глобальной переменной (вы можете проверить, набрав i или же window.i в окне консоли. Он вернет 3).
  2. Анонимная функция, которую вы объявили, не будет вызывать и проверять значение внутри функции, пока вы не вызовете функции.
  3. Когда вы вызываете функцию, console.log("My value: " + i) берет значение из своего Global объект и отобразить результат.

CASE2: используя let

Теперь замените 'var' с 'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

Сделай то же самое, иди в прицелы. Теперь вы увидите два объекта "Block" а также "Global", Теперь расширяем Block объект, вы увидите, что здесь определено "i", и странно то, что для каждой функции значение i отличается (0, 1, 2).

Заключение:

Когда вы объявляете переменную, используя 'let' даже вне функции, но внутри цикла, эта переменная не будет глобальной переменной, она станет Block переменная уровня, которая доступна только для одной и той же функции. По этой причине мы получаем значение i разные для каждой функции, когда мы вызываем функции.

Для получения более подробной информации о том, как ближе работает, пожалуйста, ознакомьтесь с удивительным видеоуроком https://youtu.be/71AtaJpJHw0

Я удивлен, что никто еще не предложил использовать forEach Функция лучше избегать (пере) использования локальных переменных. На самом деле я не использую for(var i ...) вообще больше по этой причине.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// отредактировано для использования forEach вместо карты.

Причина, по которой исходный пример не сработал, заключается в том, что все замыкания, созданные в цикле, ссылаются на один и тот же кадр. По сути, наличие 3 методов на одном объекте только с одним i переменная. Все они распечатаны одинакового значения.

Этот вопрос действительно показывает историю JavaScript! Теперь мы можем избежать видимости блоков с помощью функций стрелок и обрабатывать циклы непосредственно из узлов DOM, используя методы Object.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>

Прежде всего, поймите, что не так с этим кодом:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Здесь когда funcs[] массив инициализируется, i увеличивается, funcs массив инициализируется и размер func массив становится 3, так i = 3,, Теперь, когда funcs[j]() называется, он снова использует переменную i, который уже был увеличен до 3.

Теперь, чтобы решить это, у нас есть много вариантов. Ниже приведены два из них:

  1. Мы можем инициализировать i с let или инициализировать новую переменную index с let и сделать его равным i, Поэтому, когда звонят, index будет использоваться, и его область действия завершится после инициализации. И для звонка, index будет инициализирован снова:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
  2. Другим вариантом можно ввести tempFunc который возвращает фактическую функцию:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    

Используйте структуру замыкания, это уменьшит ваш дополнительный цикл for. Вы можете сделать это в одном цикле for:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

До ES5, эту проблему можно решить только с помощью замыкания.

Но теперь в ES6 у нас есть переменные области действия уровня блока. Изменение var, чтобы впустить первый цикл for, решит проблему.

var funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

Если у вас такая проблема с while цикл, а не for цикл, например:

var i = 0;
while (i < 5) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
  i++;
}

Техника закрытия по текущему значению немного отличается. Объявите переменную с блочной областью видимости с помощьюconst внутри while блок и назначьте текущий iк нему. Затем, где бы переменная ни использовалась асинхронно, заменитеi с новой переменной с блочной областью видимости:

var i = 0;
while (i < 5) {
  const thisIterationI = i;
  setTimeout(function() {
    console.log(thisIterationI);
  }, i * 1000);
  i++;
}

Для старых браузеров, которые не поддерживают переменные с блочной областью видимости, вы можете использовать IIFE, вызываемый с i:

var i = 0;
while (i < 5) {
  (function(innerI) {
    setTimeout(function() {
      console.log(innerI);
    }, innerI * 1000);
  })(i);
  i++;
}

Вы можете использовать декларативный модуль для списков данных, таких как query-js(*). В этих ситуациях я лично нахожу декларативный подход менее удивительным

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

Затем вы можете использовать свой второй цикл и получить ожидаемый результат, или вы могли бы сделать

funcs.iterate(function(f){ f(); });

(*) Я являюсь автором query-js и поэтому склоняюсь к его использованию, поэтому не принимайте мои слова в качестве рекомендации для указанной библиотеки только для декларативного подхода:)

И еще одно решение: вместо создания еще одного цикла, просто связать this к функции возврата.

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

Связывая это, решает проблему также.

Я предпочитаю использовать forEach Функция, которая имеет собственное замыкание с созданием псевдодальности:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

Это выглядит ужаснее, чем диапазоны в других языках, но ИМХО менее чудовищно, чем другие решения.

Используйте let(block-scope) вместо var.

var funcs = [];
for (let i = 0; i < 3; i++) {      
  funcs[i] = function() {          
    console.log("My value: " + i); 
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      
}

Ваш код не работает, потому что он делает:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

Теперь вопрос в том, каково значение переменной i когда функция вызывается? Потому что первый цикл создается с условием i < 3, он останавливается сразу, когда условие ложно, так что это i = 3,

Вы должны понимать, что во время создания ваших функций ни один из их кодов не выполняется, он сохраняется только для последующего использования. И поэтому, когда они вызываются позже, интерпретатор выполняет их и спрашивает: "Каково текущее значение i?"

Итак, ваша цель - сначала сохранить значение i функционировать и только после этого сохранить функцию в funcs, Это можно сделать, например, так:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

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

Это только один из множества способов решения этой проблемы.

Другие вопросы по тегам