请解释在循环中使用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; 

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

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

我一直在用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

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

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

为了达到这个目的,你需要创建一个新的函数,并且最后需要额外invoke the outer function立即invoke the outer function ,所以link.onclick现在已经把返回的函数作为参考。

闭包是一个构造,在这个构造中,你引用一个变量在它所定义的范围之外。 你通常在函数的上下文中讨论闭包。

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

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

附录

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

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

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

如果你没有这个所有链接点击会导致一个警告说“5”

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

..fredrik