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

Please explain the use of JavaScript closures in loops

本问题已经有最佳答案,请猛点这里访问。

我已经阅读了很多关于循环中闭包和闭包的解释。我很难理解这个概念。我有这段代码:有没有一种方法可以尽可能地减少代码,以便使闭包的概念更加清晰。我很难理解i在两个圆括号内的部分。谢谢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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上写的一篇文章中复制的:

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

1
2
3
4
5
6
7
  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = (function(x){
      return function(){
        alert(x);
      }
    })(i);
  }

或者更容易阅读的版本:

1
2
3
4
5
6
7
8
9
  function generateMyHandler (x) {
    return function(){
      alert(x);
    }
  }

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = generateMyHandler(i);
  }

这常常会让那些不熟悉JavaScript或函数式编程的人感到困惑。这是由于误解了闭包是什么。

闭包不仅仅传递变量的值,甚至传递对变量的引用。一个闭包捕获变量本身!下面的代码说明了这一点:

1
2
3
  var message = 'Hello!';
  document.getElementById('foo').onclick = function(){alert(message)};
  message = 'Goodbye!';

单击元素"foo"将生成一个带有消息"再见"的警告框。。因此,在循环中使用简单的闭包将导致所有闭包共享同一个变量,并且该变量将包含循环中分配给它的最后一个值。例如:

1
2
3
  for (var i=0; i<10; i++) {
    document.getElementById('something'+i).onclick = function(){alert(i)};
  }

单击时,所有元素都将生成一个数字为10的警报框。实际上,如果现在执行i="hello";,所有元素现在都将生成"hello"警报!变量i在十个函数加上当前函数/作用域/上下文之间共享。把它看作一种只有相关函数才能看到的私有全局变量。

我们需要的是该变量的一个实例,或者至少是对变量的一个简单引用,而不是变量本身。幸运的是,javascript已经有了一种传递引用(对象)或值(字符串和数字)的机制:函数参数!

在javascript中调用函数时,如果函数是对象,则通过引用传递该函数的参数;如果函数是字符串或数字,则通过值传递。这足以打破闭包中的变量共享。

所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  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编程已经很长时间了,"循环中的闭包"是一个非常广泛的主题。我假设您正在讨论在for循环中使用EDOCX1[1]的实践,以便在内部函数稍后执行时保留循环的"当前值"…

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

输出:

1
2
3
4
i==4, x==0
i==4, x==1
i==4, x==2
i==4, x==3

从输出中可以看到,所有内部回调函数都指向同一个i,但是,由于每个函数都有自己的"closure",所以x的值实际上存储为执行外部函数时i的值。

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


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

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

为了实现这一点,您需要创建一个新的函数,再加上在结尾处需要额外的偏执,立即对invoke the outer function,所以link.onclick现在将返回的函数作为引用。


闭包是一种构造,在这种构造中,您引用的变量超出了定义它的范围。通常在函数上下文中讨论闭包。

1
2
3
4
5
6
7
8
9
10
11
12
var helloFunction;
var finished = false;

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

helloFunction();

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

补遗

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

  • 您定义了一些函数(num)。这被称为匿名函数,因为它是内联定义的,没有名称。
  • 函数(num)接受一个整型参数,并返回对另一个函数的引用,该函数定义为alert(num)
  • 立即调用外部匿名函数,参数为i.so num=i。此调用的结果是一个将执行警报(i)的函数。
  • 最终结果或多或少相当于:link.onclick = function() { alert(i); };

  • 回答最后一部分问题。两个圆括号像调用其他函数一样调用该函数。你在这里做这个的原因是你想要保持变量"i"在那个时候是什么。所以它所做的是,调用函数,将i作为参数"num"发送。因为它是被调用的,所以它将记住变量链接中的nume值。

    如果您没有链接到所有链接,单击将导致一个警告,提示"5"

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

    弗雷德里克