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