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

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

Ответы

Ответ 1

Проблема в том, что переменная 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++) {
  // and now let run each one to see
  funcs[j]();
}

Ответ 2

Пытаться:

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

Ответ 3

Еще один способ, который еще не упоминался, - использование 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]();
}

Ответ 4

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

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

}

Ответ 5

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

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

 //перезаписать console.log(), чтобы вы могли видеть вывод консоли
console.log = function (msg) {document.body.innerHTML + = '<p>' + msg + '</p>';};

var funcs = {};
для (var я = 0; я < 3; я ++) {
   var ilocal = i;//создаем новую локальную переменную
   funcs [i] = function() {
       console.log( "Мое значение:"  + +);//каждый должен ссылаться на свою локальную переменную
   };
}
для (var j = 0; j < 3; j ++) {
   funcs [J]();
}Код>

Ответ 6

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

var funcs = [];

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

Ответ 7

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

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

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

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

Ответ 8

Что вам нужно понять, так это то, что область действия переменных в javascript основана на функции. Это важное отличие, чем, скажем, С#, где у вас есть область видимости блока, и просто скопировать переменную в одну из 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]();
}

Ответ 9

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

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

Ответ 10

Это описывает распространенную ошибку с использованием закрытий в 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 для закрытия

Ответ 11

Самое простое решение:

Вместо использования:

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

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

Ответ 12

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

  • нет массива

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


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

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

http://jsfiddle.net/7P6EN/

Ответ 13

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

var funcs = [];
[0,1,2].forEach(function(i) {          // let 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 run each one to see
}

Ответ 14

Основная проблема с кодом, показанным 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); 
    };
};

Ответ 15

Функции 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]()
}

Ответ 16

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

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

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

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner 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, либо используйте анонимную функцию, такую как Bjorn:

funcs = {};
function outer(i) {              // function outer 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 когда выполняется внутреннее.

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

Ответ 17

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

var funcs = [];
for (let i = 0; i < 3; i++) {          // let 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 run each one to see
}

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

Ответ 18

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

Ответ 19

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

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

Ответ 20

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

Ответ 21

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

var funcs = [];
for (var i = 0; i < 3; i++) {          // let 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 run each one to see
}

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

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

  • Мы можем инициализировать 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]();                        
    }
    
  • Другой вариант - ввести 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]();                        
    }
    

Ответ 22

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

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

Ответ 23

Мы будем проверять, что на самом деле происходит, когда вы заявляете, 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.

enter image description here

enter image description here

Вывод:

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

enter image description here

Вывод:

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

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

Ответ 24

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

Это выглядит уродливее, чем диапазоны на других языках, но IMHO менее чудовищно, чем другие решения.

Ответ 25

И еще одно решение: вместо создания другого цикла просто свяжите 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
}

Ответ 26

Используйте ключевое слово ECMA-6 let, чтобы связать область видимости с блоком

var funcs = [];
for (let i = 0; i < 3; i++) {      // let 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 run each one to see
} 

Ответ 27

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

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

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

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

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

Ответ 28

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

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 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 run each one to see
}

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

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

Ответ 29

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

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

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

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

Ответ 30

var funcs = [];
for (var i = 0; i < 3; i++) {      // let 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 run each one to see with j
}