Не поддерживает ли поддержка JavaScript локальные переменные?

Я очень озадачен этим кодом:

var closures = [];
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = function() {
      alert("i = " + i);
    };
  }
}

function run() {
  for (var i = 0; i < 5; i++) {
    closures[i]();
  }
}

create();
run();

По моему мнению, он должен печатать 0,1,2,3,4 (разве это не концепция закрытия?).

Вместо этого он печатает 5,5,5,5,5.

Я попробовал Rhino и Firefox.

Может ли кто-нибудь объяснить это поведение мне? спасибо заранее.

Ответ 1

Исправлен ответ Jon, добавив дополнительную анонимную функцию:

function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = (function(tmp) {
        return function() {
          alert("i = " + tmp);
        };
    })(i);
  }
}

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

После завершения цикла переменная уровня i имеет значение 5 и то, что внутренняя функция видит.


В качестве побочного примечания: вы должны остерегаться ненужного создания объекта функции, особенно в циклах; это неэффективно, и если задействованы объекты DOM, легко создавать циклические ссылки и, следовательно, внедрять утечки памяти в Internet Explorer.

Ответ 2

Я думаю, это может быть то, что вы хотите:

var closures = [];

function createClosure(i) {
    closures[i] = function() {
        alert("i = " + i);
    };
}

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}

Ответ 3

Решение состоит в том, чтобы выполнить самообучающийся лямбда-перенос вашего массива. Вы также передаете мне аргумент в эту лямбду. Значение я внутри самоисполняющейся лямбда будет затенять значение оригинала i, и все будет работать по назначению:

function create() {
    for (var i = 0; i < 5; i++) (function(i) {
        closures[i] = function() {
            alert("i = " + i);
        };
    })(i);
}

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

function create() {
    for (var i = 0; i < 5; i++) (function() {
        var x = i;

        closures.push(function() {
            alert("i = " + x);
        });
    })();
}

Ответ 4

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

Изменить: Эта статья г-на Скита объясняет закрытие в некоторой степени и решает эту проблему, в частности, в способ, который намного более информативен, тогда я здесь. Однако будьте осторожны, так как способ закрытия Javascript и С# имеет некоторые тонкие отличия. Перейдите к разделу "Сравнение стратегий захвата: сложность и сила" для его объяснения по этой проблеме.

Ответ 5

John Resig Изучение расширенного JavaScript объясняет это и многое другое. Это интерактивная презентация, в которой много объясняется JavaScript, и примеры интересны для чтения и исполнения.

В нем есть глава о закрытии, а этот пример очень похож на ваш.

Вот разбитый пример:

var count = 0; 
for ( var i = 0; i < 4; i++ ) { 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
}

И исправление:

var count = 0; 
for ( var i = 0; i < 4; i++ ) (function(i){ 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
})(i);

Ответ 6

Просто определяя внутреннюю функцию или присваивая ее некоторой переменной:

closures[i] = function() {...

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

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

Ответ 7

Вот что вам нужно сделать для достижения вашего результата:

<script>
var closures = [];
function create() {  
    for (var i = 0; i < 5; i++) {   
        closures[i] = function(number) {      
        alert("i = " + number);   
        };  
    }
}
function run() {  
    for (var i = 0; i < 5; i++) {   
        closures[i](i); 
    }
}
create();
run();
</script>