请解释 JavaScript 闭包在循环中的使用

IT技术 javascript loops scope closures
2021-02-08 18:37:44

我已经阅读了许多关于闭包和循环内闭包的解释。我很难理解这个概念。我有这个代码:有没有办法尽可能地减少代码,这样闭包的概念就可以更清楚了。我很难理解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;
5个回答

警告:长(ish)答案

这是直接从我在公司内部维基上写的一篇文章中复制的:

问题:如何在循环中正确使用闭包?快速回答:使用函数工厂。

  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";所有元素现在都会生成一个“你好”警报!变量 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 */
  }
这是我见过的最好的闭包解释之一。晶莹剔透。
2021-03-11 18:37:44
非常感谢大家的帮助。事实证明,我无法掌握闭包的概念,尤其是 (i) 函数的原因之一,是因为我缺乏对以这种方式调用的匿名函数的理解。看,我太习惯了 :myfunction() 的做法,就像 () 一样调用函数让我很困惑。因此,如果有人对此括号感到困惑,请考虑 myfunction(param) 的概念……但是在 javascript 版本中:(param) ……谢谢大家。
2021-03-15 18:37:44
伙计,我只想说声谢谢。在过去的五个小时里,你简直一针见血!祝福你
2021-04-08 18:37:44
致 OP:我在您添加示例代码之前就开始打字了。所以我使用我自己的示例代码。希望你不要介意。
2021-04-10 18:37:44
“闭包不仅仅传递变量的值,甚至是对变量的引用。闭包捕获变量本身!” 我喜欢这个解释。
2021-04-10 18:37:44

我已经用 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在您调用“外部函数”时引用

我知道如果我们用 let 关键字声明“i”变量而不是用 var 关键字声明问题将得到解决,请您解释一下“为什么使用 let 而不是 var 来解决问题”?
2021-04-09 18:37:44

好吧,在这种情况下闭包的“问题”是,任何访问i都会引用相同的变量。那是因为ECMA-/Javascripts function scopelexical scope

因此,为了避免每次调用alert(i);都会显示 a 5(因为在循环完成后 i === 5),您需要创建一个在运行时调用自身的新函数。

要实现这一点,您需要创建一个新函数,另外还需要在末尾添加额外的括号,toinvoke the outer function立即,因此link.onclick现在将返回的函数作为参考。

闭包是一种结构,您可以在其中引用定义它的范围之外的变量。您通常在函数的上下文中谈论闭包。

var helloFunction;
var finished = false;

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

helloFunction();

在这里,我定义了变量message,并定义了一个引用message的函数当我定义使用消息的函数时我正在创建一个闭包。这意味着helloFunction持有对message的引用,以便我可以继续使用message,即使在定义message的范围(循环体)之外

附录

括号中的 (i) 是一个函数调用。发生的事情是:

  1. 您定义了一些函数(数量){}。这称为匿名函数,因为它是内联定义的并且没有名称。
  2. function(num) 接受一个整数参数,并返回对另一个函数的引用,该函数定义为 alert(num)
  3. 使用参数i立即调用外部匿名函数所以num = i此调用的结果是一个执行 alert(i) 的函数。
  4. 最终结果或多或少相当于: link.onclick = function() { alert(i); };
别客气!如果这解决了您的问题,请单击复选标记将答案标记为已接受。Stackoverflow 非常适合快速获得答案 =)
2021-03-31 18:37:44
哇,这是一个快速的答案,这是我的第一个问题。谢谢
2021-04-01 18:37:44

回答你问题的最后一部分。两个括号像调用任何其他函数一样调用该函数。你在这里这样做的原因是你想保留当时变量“i”的内容。所以它所做的是,调用该函数,将 i 作为参数“num”发送。由于它被调用,它会记住变量链接自己的勺子中的值 nume。

如果你不这样做,所有的链接点击都会导致一个警告说“5”

jQuery 的创始人 John Resig 有一个非常好的在线演示文稿解释了这一点。http://ejohn.org/apps/learn/

..弗雷德里克