Javascript печально известный вопрос петли?
У меня есть следующий фрагмент кода.
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
alert(i);
};
document.body.appendChild(link);
}
}
Приведенный выше код предназначен для создания 5 ссылок и привязки каждой ссылки к событию оповещения, чтобы показать текущий идентификатор ссылки. Но это не работает. Когда вы нажимаете на сгенерированные ссылки, они все говорят "ссылка 5".
Но следующий фрагмент кода работает как наше ожидание.
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}
Вышеуказанные 2 фрагмента приведены здесь. Как объясняет автор, похоже, замыкание делает магию.
Но как это работает и как закрытие заставляет его работать - все это за пределами моего понимания. Почему первый не работает, а второй работает? Кто-нибудь может дать подробное объяснение о магии?
Благодарю.
5 ответов
Цитирую себя для объяснения первого примера:
Области применения JavaScript - на уровне функций, а не на уровне блоков, и создание замыкания просто означает, что включающая область видимости добавляется в лексическое окружение вложенной функции.
После завершения цикла переменная уровня функции i получает значение 5, и именно это "видит" внутренняя функция.
Во втором примере для каждого шага итерации литерал внешней функции будет преобразован в новый объект функции со своей областью действия и локальной переменной. num
, чье значение установлено на текущее значение i
, Как num
никогда не изменяется, он будет оставаться постоянным в течение всего времени закрытия: следующий шаг итерации не перезаписывает старое значение, так как объекты функции независимы.
Имейте в виду, что этот подход довольно неэффективен, поскольку для каждой ссылки необходимо создать два новых функциональных объекта. Это не нужно, так как ими можно легко поделиться, если вы используете DOM-узел для хранения информации:
function linkListener() {
alert(this.i);
}
function addLinks () {
for(var i = 0; i < 5; ++i) {
var link = document.createElement('a');
link.appendChild(document.createTextNode('Link ' + i));
link.i = i;
link.onclick = linkListener;
document.body.appendChild(link);
}
}
Мне нравится писать простые объяснения для толстых людей, потому что я толстый, так что здесь идет...
У нас есть 5 div на странице, каждый с идентификатором... div1, div2, div3, div4, div5
JQuery может сделать это...
for (var i=1; i<=5; i++) {
$("#div" + i).click ( function() { alert ($(this).index()) } )
}
Но на самом деле решение проблемы (и наращивание это медленно) ...
ШАГ 1
for (var i=1; i<=5; i++) {
$("#div" + i).click (
// TODO: Write function to handle click event
)
}
ШАГ 2
for (var i=1; i<=5; i++) {
$("#div" + i).click (
function(num) {
// A functions variable values are set WHEN THE FUNCTION IS CALLED!
// PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
// Now the click event is expecting a function as a handler so return it
return function() { alert (num) }
}(i) // We call the function here, passing in i
)
}
ПРОСТО ПОНИМАТЬ АЛЬТЕРНАТИВУ
Если вы не можете обдумать это, тогда это должно быть легче для понимания и имеет тот же эффект...
for (var i=1; i<=5; i++) {
function clickHandler(num) {
$("#div" + i).click (
function() { alert (num) }
)
}
clickHandler(i);
}
Это должно быть легко понять, если вы помните, что значения переменных функций устанавливаются при вызове функции (но при этом используется тот же самый мыслительный процесс, что и раньше)
По сути, в первом примере вы связываете i
внутри onclick
обработчик непосредственно к i
вне onclick
обработчик. Итак, когда i
вне onclick
изменения обработчика, i
внутри onclick
меняется и обработчик.
Во втором примере вместо привязки к num
в onclick
обработчик, вы передаете его в функцию, которая затем связывает его с num
в onclick
обработчик. Когда вы передаете его в функцию, значение i
копируется, не связан с num
, Так когда i
изменения, num
остается такой же. Копирование происходит потому, что функции в JavaScript являются "замыканиями", а это означает, что как только что-то передается в функцию, оно "закрывается" для внешней модификации.
Другие объяснили, что происходит, вот альтернативное решение.
function addLinks () {
for (var i = 0, link; i < 5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
with ({ n: i }) {
link.onclick = function() {
alert(n);
};
}
document.body.appendChild(link);
}
}
В основном, бедняков, пусть связывающих.
В первом примере вы просто привязываете эту функцию к событию onclick:
function() {alert(i);};
Это означает, что при событии щелчка js должно сообщать значение переменной addlink functions i. Его значение будет 5 из-за цикла for ().
Во втором примере вы генерируете функцию, которая будет связана с другой функцией:
function (num) {
return function () { alert(num); };
}
Это означает: если вызывается со значением, верните мне функцию, которая оповестит входное значение. Например, звоню function(3)
вернусь function() { alert(3) };
,
Вы вызываете эту функцию со значением i на каждой итерации, таким образом, вы создаете отдельные функции onclick для каждой ссылки.
Дело в том, что в первом примере ваша функция содержала ссылку на переменную, а во втором с помощью внешней функции вы заменили ссылку на фактическое значение. Это называется закрытием примерно потому, что вы "заключаете" текущее значение переменной в вашу функцию вместо того, чтобы хранить ссылку на нее.