Разве 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.
Решение состоит в том, чтобы иметь самопроизвольную лямбду, упаковывающую ваш массив. Вы также передаете i в качестве аргумента этой лямбде. Значение 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);
});
})();
}
Я думаю, что это может быть то, что вы хотите:
var closures = [];
function createClosure(i) {
closures[i] = function() {
alert("i = " + i);
};
}
function create() {
for (var i = 0; i < 5; i++) {
createClosure(i);
}
}
Да закрытия здесь работают. Каждый раз, когда вы зацикливаете созданную вами функцию, захватывает i
, Каждая функция, которую вы создаете, делится одинаково i
, Проблема, которую вы видите, состоит в том, что, поскольку они все имеют одинаковые i
они также разделяют окончательную стоимость i
так как это та же самая захваченная переменная.
Редактировать: Эта статья г-на Скита объясняет замыкания в некоторой глубине и рассматривает эту проблему, в частности, гораздо более информативно, чем я здесь. Однако будьте осторожны, так как 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>