Пожалуйста, объясните использование закрытий JavaScript в циклах

Я прочитал ряд пояснений о замыканиях и замыканиях внутри петель. Мне сложно понять концепцию. У меня есть этот код: есть ли способ уменьшить код как можно больше, поэтому концепция закрытия может быть понятнее. Я с трудом понимаю часть, в которой i находится внутри двух скобок. Благодаря

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

    }
}
window.onload = addLinks;

Ответ 1

ПРЕДУПРЕЖДЕНИЕ: длинный (иш) ответ

Это копируется непосредственно из статьи, которую я написал во внутренней вики компании:

Вопрос: Как правильно использовать замыкания в цикле? Быстрый ответ: используйте функцию factory.

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = (function(x){
      return function(){
        alert(x);
      }
    })(i);
  }

или более легко читаемая версия:

  function generateMyHandler (x) {
    return function(){
      alert(x);
    }
  }

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = generateMyHandler(i);
  }

Это часто путают людей, которые не знакомы с javascript или функциональным программированием. Это результат непонимания того, что такое закрытие.

Закрытие не просто передает значение переменной или даже ссылку на переменную. Закрытие захватывает переменную! Следующий бит кода иллюстрирует это:

  var message = 'Hello!';
  document.getElementById('foo').onclick = function(){alert(message)};
  message = 'Goodbye!';

Щелчок по элементу 'foo' создаст окно с сообщением: "До свидания!". Из-за этого использование простого замыкания в цикле закончится тем, что все блокировки используют одну и ту же переменную, и эта переменная будет содержать последнее значение, назначенное ему в цикле. Например:

  for (var i=0; i<10; i++) {
    document.getElementById('something'+i).onclick = function(){alert(i)};
  }

Все элементы при нажатии будут генерировать окно предупреждения с номером 10. На самом деле, если мы теперь делаем i="hello";, все элементы теперь генерируют предупреждение "привет"! Переменная я делится на десять функций. PLUS - текущая функция/область/контекст. Подумайте об этом как о какой-то частной глобальной переменной, которую могут видеть только функции.

Нам нужен экземпляр этой переменной или, по крайней мере, простая ссылка на переменную вместо самой переменной. К счастью, у javascript уже есть механизм для передачи ссылки (для объектов) или значения (для строк и чисел): аргументы функции!

Когда функция вызывается в javascript, аргументы этой функции передаются по ссылке, если она является объектом или значением, если она является строкой или номером. Этого достаточно, чтобы разделить распределение переменных в закрытии.

Итак:

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick =
      (function(x){ /* we use this function expression simply as a factory
                       to return the function we really want to use: */

        /* we want to return a function reference
           so we write a function expression*/
        return function(){
          alert(x); /* x here refers to the argument of the factory function
                       captured by the 'inner' closure */
        }

      /* The brace operators (..) evaluates an expression, in this case this
         function expression which yields a function reference. */

      })(i) /* The function reference generated is then immediately called()
               where the variable i is passed */
  }

Ответ 2

Я программировал в JavaScript в течение длительного времени, а "закрытие в цикле" - очень широкая тема. Я предполагаю, что вы говорите о практике использования (function(param) { return function(){ ... }; })(param); внутри цикла for, чтобы сохранить "текущее значение" цикла, когда эта внутренняя функция будет выполняться позже...

Код:

for(var i=0; i<4; i++) {
  setTimeout(
    // argument #1 to setTimeout is a function.
    // this "outer function" is immediately executed, with `i` as its parameter
    (function(x) {
      // the "outer function" returns an "inner function" which now has x=i at the
      // time the "outer function" was called
      return function() {  
        console.log("i=="+i+", x=="+x);
      };
    })(i) // execute the "closure" immediately, x=i, returns a "callback" function
  // finishing up arguments to setTimeout
  , i*100);
}

Вывод:

i==4, x==0
i==4, x==1
i==4, x==2
i==4, x==3

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

Обычно, когда вы видите этот шаблон, вы должны использовать то же имя переменной, что и параметр, и аргумент внешней функции: (function(i){ })(i) например. Любой код внутри этой функции (даже если он будет выполнен позже, как функция обратного вызова) будет ссылаться на i в момент, когда вы вызывали "внешнюю функцию".

Ответ 3

Ну, "проблема" с закрытием в таком случае заключается в том, что любой доступ к i будет ссылаться на одну и ту же переменную. Это происходит из-за ECMA-/Javascripts function scope или lexical scope.

Поэтому, чтобы каждый вызов alert(i); отображал 5 (потому что после завершения цикла я === 5) вам нужно создать новую функцию, которая запускается во время выполнения.

Чтобы достичь этого, вам нужно создать новую функцию, плюс вам понадобится дополнительная парантеза в конце, до invoke the outer function сразу, поэтому link.onclick теперь возвращает возвращаемую функцию в качестве ссылки.

Ответ 4

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

var helloFunction;
var finished = false;

while (!finished) {
 var message = 'Hello, World!';
 helloFunction = function() {
   alert(message);
 }
 finished = true;
}

helloFunction();

Здесь я определяю сообщение переменной и определяю функцию, которая ссылается на сообщение. Когда я определяю функцию для использования сообщения, я создаю закрытие. Это означает, что helloFunction содержит ссылку на сообщение, так что я могу продолжать использовать сообщение, даже вне области (тело цикла), где определено сообщение.

Добавление

В скобках (i) есть вызов функции. Что происходит:

  • Вы определяете некоторую функцию (num) {}. Это называется анонимной функцией, потому что она определена внутри и не имеет имени. Функция
  • (num) принимает целочисленный аргумент и возвращает ссылку на другую функцию, которая определяется как alert (num)
  • Внешняя анонимная функция немедленно вызывается с аргументом i. Итак, num = i. Результатом этого вызова является функция, которая будет предупреждать (i).
  • Конечный результат более или менее эквивалентен: link.onclick = function() { alert(i); };

Ответ 5

Чтобы ответить на последнюю часть ваших вопросов. Две скобки вызывают функцию как любые другие функции. Почему вы делаете это здесь, так это то, что вы хотите сохранить то, что переменная "i" находится именно в то время. Итак, что он делает, вызывается функция, я отправляется как аргумент "num". Поскольку он вызывается, он будет помнить значение nume в переменных ссылках собственного совка.

Если вы не сделали этого, нажмите ссылку "5"

Джон Ресиг, основатель jQuery, имеет действительно приятную онлайн-презентацию, объясняющую это. http://ejohn.org/apps/learn/

.. Фредрик