Не поддерживает ли поддержка 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>