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

时间:2010-08-26 06:16:01

标签: javascript loops scope closures

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

答案 0 :(得分:108)

警告:长(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'将生成一个警告框,其中包含以下消息:“Goodbye!”。因此,在循环中使用简单闭包将最终使所有闭包共享相同的变量,并且该变量将包含在循环中分配给它的最后一个值。例如:

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

答案 1 :(得分:10)

我已经用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的值实际存储就像外部函数执行时的i一样。

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

答案 2 :(得分:3)

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

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

要实现这一点,您需要创建一个新函数,并且最后需要额外的paranthesis,立即invoke the outer function,因此link.onclick现在已将返回的函数作为参考。

答案 3 :(得分:2)

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

var helloFunction;
var finished = false;

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

helloFunction();

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

<强>附录

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

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

答案 4 :(得分:0)

回答问题的最后部分。两个括号将该函数作为任何其他函数调用。你为什么这样做是因为你想保留变量“i”就在那个时候。它的作用是,调用函数,i作为参数“num”发送。因为它被调用它将记住变量链接自己的scoop中的值nume。

如果你没有这个全部链接点击将导致提示“5”

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

..弗雷德里克