Как работает функция в цикле (которая возвращает другую функцию)?
Я пытаюсь назначить функцию для события onclick динамически созданного тега "a" в JavaScript. Все теги создаются в цикле следующим образом:
for ( var i = 0; i < 4; i++ )
{
var a = document.createElement( "a" );
a.onclick = function( ) { alert( i ) };
document.getElementById( "foo" ).appendChild( a );
}
Предупреждающее значение для всех четырех ссылок всегда "4". Довольно очевидно. Когда googling я наткнулся на сообщение, которое показывает следующий фрагмент кода:
a.onclick = (function(p, d) {
return function(){ show_photo(p, d) }
})(path, description);
Мне удалось настроить его для моих нужд и я получил предупреждение (i), чтобы работать правильно, но я буду признателен, если кто-нибудь сможет объяснить, что именно делает этот код.
Ответы
Ответ 1
Когда вы назначаете функцию обработчику кликов, создается closure.
Как правило, замыкание происходит, когда вы вставляете функции, внутренние функции могут ссылаться на переменные, присутствующие в их внешних закрывающих функциях, даже после того, как их родительские функции уже выполнены.
В момент выполнения события click обработчик ссылается на последнее значение, которое имела переменная i
, поскольку эта переменная хранится в закрытии.
Как вы заметили, обернув функцию обработчика кликов, чтобы принять переменную i
в качестве аргумента и вернуть другую функцию (в основном создать другое закрытие), она работает так, как вы ожидаете:
for ( var i = 0; i < 4; i++ ) {
var a = document.createElement( "a" );
a.onclick = (function(j) { // a closure is created
return function () {
alert(j);
}
}(i));
document.getElementById( "foo" ).appendChild( a );
}
Когда вы выполняете итерацию, на самом деле создаете 4 функции, каждая функция сохраняет ссылку на i
в момент ее создания (путем передачи i
), это значение сохраняется во внешнем закрытии, а внутренняя функция выполняется, когда событие клика срабатывает.
Я использую следующий фрагмент для объяснения замыканий (и очень простая концепция curry), я думаю, что простой пример может сделать проще получить концепцию:
// a function that generates functions to add two numbers
function addGenerator (x) { // closure that stores the first number
return function (y){ // make the addition
return x + y;
};
}
var plusOne = addGenerator(1), // create two number adding functions
addFive = addGenerator(5);
alert(addFive(10)); // 15
alert(plusOne(10)); // 11
Ответ 2
Не вдаваясь в подробности, это существенно создает копии переменных экземпляра, обертывая их в функцию, которая выполняется немедленно и передает обратно функции, которая будет выполняться при нажатии элемента.
Подумайте об этом так:
function() { alert(i); } // Will expose the latest value of i
(function(I) { return function() { alert(I); }; })(i); // Will pass the current
// value of i and return
// a function that exposes
// i at that time
Таким образом, во время каждой итерации цикла вы фактически выполняете функцию, которая возвращает функцию с текущим значением переменной.
Что, если вы представляете себе, что в вашем цикле у вас 4 якоря, вы создаете 4 отдельные функции, которые можно визуализировать как...
function() { alert(0); };
function() { alert(1); };
function() { alert(2); };
function() { alert(3); };
Я бы рассмотрел вопрос о масштабах и закрытиях с помощью javascript, как если бы вы пошли по этому пути и не понимаете, что именно происходит, вы можете столкнуться с серьезными проблемами из-за неожиданного поведения.
Ответ 3
Когда запускается событие onclick, вызывается анонимная функция и относится к той же переменной i
, которая была использована в цикле, и она содержит последнее значение i
, которое равно 4.
Решение вашей проблемы заключается в использовании функции, возвращающей функцию:
a.onclick = (function(k) {return function() { alert(k); }; })(i);