Как работает функция в цикле (которая возвращает другую функцию)?

Я пытался назначить функцию событию onclick динамически созданного тега в JavaScript. Все теги создаются в цикле следующим образом:

for ( var i = 0; i < 4; i++ )
{
  var a = document.createElement( "a" );
  a.onclick = function( ) { alert( i ) };
  document.getElementById( "foo" ).appendChild( a );
}

Предупреждаемое значение для всех четырех ссылок всегда равно 4. Довольно очевидно. При поиске в Google я наткнулся на сообщение, в котором показан следующий фрагмент кода:

a.onclick = (function(p, d) {
return function(){ show_photo(p, d) }
})(path, description);

Мне удалось настроить его под свои нужды, и функция alert (i) работает правильно, но я буду признателен, если кто-нибудь сможет точно объяснить, что делает приведенный выше код.


person Salman A    schedule 12.10.2009    source источник
comment
Привет, Не могли бы вы сказать мне, почему он оповещает 4? Разве не должно быть 2? Спасибо.   -  person Tarik    schedule 12.10.2009
comment
for (var i = 0; i ‹3; i ++) оставляет в конце i == 4   -  person Salman A    schedule 12.10.2009
comment
Мое плохое ... цикл должен был выполняться для i = от 0 до 3, поэтому либо i ‹= 3, либо i‹ 4.   -  person Salman A    schedule 12.10.2009


Ответы (3)


Когда вы назначаете функцию обработчику кликов, создается закрытие.

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

Во время выполнения события щелчка обработчик обращается к последнему значению переменной 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), это значение сохраняется во внешнем замыкании, а внутренняя функция выполняется при возникновении события щелчка.

Я использую следующий фрагмент, чтобы объяснить замыкания (и очень простую концепцию карри), Я думаю, что простой пример может облегчить понимание концепции:

// 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
person Christian C. Salvadó    schedule 12.10.2009
comment
Тейлор, я рад за тебя, я позволю тебе закончить, но этот пост - лучший за все время и за все время. - person Tarik; 12.10.2009
comment
Не зря у вас 42к баллов :) - person Salman A; 12.10.2009
comment
Однажды люди забудут о Канье Уэсте, как и я, и все мемы, относящиеся к нему, будут казаться грубыми и немного странными. - person RandomInsano; 23.06.2010

Не вдаваясь в подробности, это, по сути, создает копии переменных экземпляра, оборачивая их в функцию, которая выполняется немедленно и передает обратно функции, которая будет выполнена при щелчке по элементу.

Подумайте об этом так:

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, как если бы вы идете по этому пути и не понимаете, что именно происходит, вы можете столкнуться с серьезными проблемами из-за неожиданного поведения.

person Quintin Robinson    schedule 12.10.2009
comment
Спасибо за объяснение. Хороший прием - или в javascript все так же? - person Amarghosh; 12.10.2009

Когда запускается событие onclick, вызывается анонимная функция, которая обращается к той же переменной i, которая использовалась в цикле, и содержит последнее значение i, то есть 4.

Решение вашей проблемы - использовать функцию, возвращающую функцию:

a.onclick = (function(k) {return function() { alert(k); }; })(i);
person Maurice Perry    schedule 12.10.2009