【问题标题】:Please explain the use of JavaScript closures in loops [duplicate]请解释在循环中使用 JavaScript 闭包 [重复]
【发布时间】:2011-04-04 02:27:36
【问题描述】:

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

【问题讨论】:

    标签: javascript loops scope closures


    【解决方案1】:

    警告:长(ish)答案

    这是直接复制自我在公司内部 wiki 上写的一篇文章:

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

      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 */
      }
    

    【讨论】:

    • 致 OP:我在您添加示例代码之前就开始打字了。所以我正在使用我自己的示例代码。希望你不要介意。
    • "闭包不仅仅传递变量的值,甚至是对变量的引用。闭包捕获了变量本身!"我喜欢这个解释。
    • 非常感谢大家的帮助。事实证明,我无法理解闭包的概念,尤其是 (i) 函数的原因之一是因为我缺乏对以这种方式调用匿名函数的理解。看,我已经习惯了 :myfunction() 的做法,以至于像 () 一样调用函数让我很困惑。因此,如果有人对该括号感到困惑,请考虑 myfunction(param) 的概念......但在 javascript 版本中:(param)......谢谢大家。
    • 这是我见过的最好的闭包解释之一。晶莹剔透。
    • 伙计,我只想说声谢谢。花了过去五个小时,你刚刚击中了头上的钉子!祝福你
    【解决方案2】:

    我已经用 JavaScript 编程很长时间了,“循环中的闭包”是一个非常广泛的话题。我假设您正在谈论在 for 循环中使用 (function(param) { return function(){ ... }; })(param); 的做法,以便在稍后执行该内部函数时保留循环的“当前值”...

    代码:

    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 的值实际上存储为@987654326 @ 在外部函数执行时。

    通常当你看到这种模式时,你会使用相同的变量名作为参数和外部函数的参数:例如(function(i){ })(i)。该函数内的任何代码(即使稍后执行,如回调函数)在您调用“外部函数”时都会引用i

    【讨论】:

    • 我知道如果我们用let关键字声明“i”变量而不是用var关键字声明问题将得到解决,请您解释一下“为什么使用let而不是var来解决问题“?
    【解决方案3】:

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

    所以为了避免每次调用alert(i); 都会显示5(因为在循环完成后 i === 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();
      

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

      附录

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

      1. 您定义了一些函数(num) {}。这称为匿名函数,因为它是内联定义的并且没有名称。
      2. function(num) 接受一个整数参数,并返回对另一个函数的引用,该函数定义为 alert(num)
      3. 立即调用外部匿名函数,参数为 i。所以 num=i。此调用的结果是一个执行 alert(i) 的函数。
      4. 最终结果或多或少等同于:link.onclick = function() { alert(i); };

      【讨论】:

      • 哇,答案很快,这是我的第一个问题。谢谢
      • 不客气!如果这解决了您的问题,请通过单击复选标记将答案标记为已接受。 Stackoverflow 非常适合快速获得答案 =)
      【解决方案5】:

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

      如果您不这样做,所有链接点击会导致警报显示“5”

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

      ..弗雷德里克

      【讨论】:

        猜你喜欢
        • 2013-12-25
        • 2013-08-31
        • 1970-01-01
        • 1970-01-01
        • 2019-02-13
        • 2018-10-15
        • 1970-01-01
        • 2015-03-01
        相关资源
        最近更新 更多