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

На этот вопрос уже есть ответ здесь:

Я прочитал ряд объяснений о замыканиях и замыканиях внутри циклов. Мне трудно понять концепцию. У меня есть этот код: есть ли способ уменьшить код как можно больше, чтобы концепция замыкания можно было сделать более понятным. Мне трудно понять, в какой части 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;
56
задан Michał Perłakowski 25 November 2016 в 22:30
поделиться

5 ответов

ВНИМАНИЕ: Длинный ответ

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

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

  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"; все элементы теперь будут генерировать предупреждение "hello"! Переменная i используется десятью функциями ПЛЮС текущей функцией/областью/контекстом. Думайте об этом как о своего рода частной глобальной переменной, которую могут видеть только задействованные функции.

Нам нужен экземпляр этой переменной или, по крайней мере, простая ссылка на переменную, а не на саму переменную.К счастью, в 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 */
  }
114
ответ дан 26 November 2019 в 17:13
поделиться

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

var helloFunction;
var finished = false;

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

helloFunction();

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

Дополнение

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

  1. Вы определяете некоторую функцию (число) {}. Это называется анонимной функцией, потому что она определена встроенной и не имеет имени.
  2. function(num) принимает целочисленный аргумент и возвращает ссылку на другую функцию, которая определена как alert(num)
  3. Немедленно вызывается внешняя анонимная функция с аргументом i. Итак, число=i. Результатом этого вызова является функция, которая выполнит alert(i).
  4. Конечный результат более или менее эквивалентен следующему: link.onclick = function() { alert(i); };
2
ответ дан 26 November 2019 в 17:13
поделиться

Я давно программирую на 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 во время выполнения внешней функции.

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

10
ответ дан 26 November 2019 в 17:13
поделиться

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

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

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

4
ответ дан 26 November 2019 в 17:13
поделиться

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

Если вы этого не сделаете, все клики по ссылкам приведут к предупреждению с надписью «5»

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

..fredrik

0
ответ дан 26 November 2019 в 17:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: