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 ссылок и привязки каждой ссылки к событию оповещения, чтобы показать текущий идентификатор ссылки. Но это не сработает. Когда вы нажимаете сгенерированные ссылки, все говорят "link 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 фрагмента цитируются из здесь. Как объясняет автор, кажется, что закрытие делает магию.

Но как это работает и как закрытие заставляет его работать, все это выходит за рамки моего понимания. Почему первый не работает, пока работает второй? Может ли кто-нибудь дать подробное объяснение о магии?

спасибо.

Ответ 1

Цитата себя для объяснения первого примера:

Области JavaScript являются функциональными, а не блочными, а создание замыкания означает, что область охвата добавляется в лексическую среду закрытой функции.

После того, как цикл завершается, переменная я уровня функции имеет значение 5 и то, что внутренняя функция видит.

Во втором примере для каждого этапа итерации внешний литерал функции будет оценивать новый объект функции со своей собственной областью и локальной переменной num, значение которой установлено на текущее значение i. Поскольку num никогда не будет изменен, он останется постоянным в течение срока действия замыкания. Следующий шаг итерации не перезаписывает старое значение, поскольку объекты функции являются независимыми.

Имейте в виду, что этот подход довольно неэффективен, поскольку для каждой ссылки необходимо создать два новых объекта функций. Это необязательно, поскольку их можно легко разделить, если вы используете DOM node для хранения информации:

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);
    }
}

Ответ 2

Мне нравится писать простые объяснения для толстых людей, потому что я толстый, поэтому здесь идет...

У нас есть 5 разделов на странице, каждый с идентификатором... 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);

}

Это должно быть просто понять, если вы помните, что значения переменных функций задаются при вызове функции (но это использует тот же самый процесс мышления, что и раньше)

Ответ 3

В основном, в первом примере вы привязываете i внутри обработчика onclick непосредственно к i вне обработчика onclick. Поэтому, когда i вне обработчика onclick изменяется, также изменяется i внутри обработчика onclick.

Во втором примере вместо привязки к num в обработчике onclick вы передаете его в функцию, которая затем привязывает его к num в обработчике onclick. Когда вы передаете его в функцию, копируется значение i, не привязанное к num. Поэтому, когда i изменяется, num остается неизменным. Копия происходит потому, что функции в JavaScript являются "замыканиями", что означает, что как только что-то передается в функцию, оно "закрыто" для внешней модификации.

Ответ 4

Другие объяснили, что происходит, здесь альтернативное решение.

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);
  }
}

В основном, бедные мужчины допускают привязку.

Ответ 5

В первом примере вы просто привязываете эту функцию к событию onclick:

function() {alert(i);};

Это означает, что в событии клика js должен предупредить значение функции i-переменной addlink. Его значение будет 5 из-за цикла for().

Во втором примере вы создаете функцию, связанную с другой функцией:

function (num) {
  return function () { alert(num); };
}

Это означает: если вызывается со значением, верните мне функцию, которая будет предупреждать входное значение. Например. вызов function(3) вернет function() { alert(3) };.

Вы вызываете эту функцию со значением я на каждой итерации, поэтому вы создаете отдельные функции onclick для каждой ссылки.

Дело в том, что в первом примере ваша функция содержала ссылку на переменную, а во втором - с помощью внешней функции, которую вы заменили ссылкой фактическим значением. Это называется закрытием примерно потому, что вы "заключите" текущее значение переменной внутри вашей функции, а не сохраняете ссылку на нее.