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>

… или асинхронный код, например, с помощью обещаний:

// 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.
}

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

30 ответов


Ну, проблема в том, что переменная 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() обеспечивает чистый, естественный способ получить Идеальное закрытие для каждой итерации. То есть, если у вас есть какой-то массив, содержащий значения (Дом ссылки, объекты, что угодно), и возникает проблема настройки обратных вызовов, специфичных для каждого элемента, вы можете сделать это:

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), новейшая версия JavaScript, теперь начинает реализовываться во многих браузерах evergreen и бэкэнд-системах. Есть также transpilers как Бабель это преобразует ES6 в ES5, чтобы разрешить использование новых функций в старых системах.

ЕС6 вводит новый 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):

лично я думаю, что @Эуст-х более свежий ответ об использовании .bind это лучший способ сделать это сейчас. Есть также Lo-dash/подчеркивание _.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 не имеет области блока. Переменные, введенные с блоком, относятся к содержащей функции или скрипту, и эффекты их установки сохраняются за пределами самого блока. Другими словами, операторы block не вводят область. Хотя "отдельные" блоки синтаксис, вы не хотите использовать автономные блоки в JavaScript, потому что они не делают то, что ты думаешь, если ты думаешь, что они делать такие блоки в C или Ява.

повторил для убедительности:

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 внутренние функции имеют доступ к внешним переменным, потому что внутренние области "заключают" внешние области.

Closures

это также означает, что внутренние функции "держатся" за внешние переменные и сохраняют их живыми, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-оболочку исключительно для создания новая область, объявить 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 теперь широко поддерживается, лучший ответ на этот вопрос изменился. ЕС6 предоставляет 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. Он также поддерживается в узле, и вы можете использовать его в любом месте, используя такие инструменты сборки, как Babel. Вы можете см. рабочий пример здесь:http://jsfiddle.net/ben336/rbU4t/2/

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

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


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

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

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

просто подумал, что я добавлю объяснение для ясности. Для решение, лично я бы начал с Harto, так как это самый пояснений способ сделать это из ответов здесь. Любой из опубликованных кодов будет работать, но я бы выбрал фабрику закрытия, чтобы написать кучу комментариев, чтобы объяснить, почему я объявляю новую переменную(Фредди и 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]();                        
}

вот еще один вариант метода, похожий на Bjorn's (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] are не независимые. На самом деле, они работают на одном и том же 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 с помощью жизнь (сразу же вызывается функция-выражение) и передает new_i в качестве параметра и захватив его как i. Поскольку анонимная функция выполняется немедленно, то i значение отличается для каждой функции, определенной внутри анонимной функции.

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


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

  • нет массиве

  • нет дополнительных для цикла


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); 
    };
};

вот простое решение, которое использует 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

функции 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 цикл выполняется, создавая отдельную область для каждой функции, чтобы закрыть. Различные другие методы делают то же самое с extra функции.

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.


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

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

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


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

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]();                        
    }
    

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

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 и let по одиночке.

Case1 : используя 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.

enter image description here

enter image description here

вывод:

  1. при объявлении переменной с помощью 'var' вне функции он становится глобальной переменной(вы можете проверить, набрав i или window.i в окне консоли.Он вернется 3).
  2. функции annominous вы заявили будет не вызывайте и не проверяйте значение внутри функции, если вы не вызываете функции.
  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". Теперь разверните


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


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

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

Я предпочитаю использовать 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
}

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


можно использовать декларативный модуль для списков данных, таких как запрос-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 к функции return.

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
}

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


многие решения кажутся правильными, но они не упоминают это называется Currying, который является функциональным шаблон программирования для таких ситуаций, как здесь. 3-10 раз быстрее, чем bind в зависимости от браузера.

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

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

посмотреть прирост производительности в разных браузерах.


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

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 на каждой итерации.

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


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

СЧЕТЧИК ЯВЛЯЕТСЯ ПРИМИТИВНЫМ

давайте определим функции обратного вызова следующим образом:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

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

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

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

теперь что особенного в этом "примитивы передаются по значению и копируются. Таким образом, когда закрытие определено, они сохраняют значение из предыдущего цикла."

СЧЕТЧИК ОБЪЕКТА

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

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

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

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined