Разве 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.

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

7 53

7 ответов:

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

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.

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

var closures = [];

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

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

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

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

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

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

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

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

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

Джон в отставку Изучение Расширенного 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);

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

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

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

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

вот что вы должны сделать для достижения результата:

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