在for循环中的闭包

12

在一个循环中使用闭包会导致问题。我认为我需要创建另一个返回函数的函数来解决这个问题,但是我无法让它在我的 jQuery 代码中工作。

以下是简化形式的基本问题:

function foo(val) {
  alert(val);
}

for (var i = 0; i < 3; i++) {
  $('#button'+i).click(function(){
    foo(i);
  });
}

自然点击任何一个按钮都会弹出一个提示框,显示数字3。我想要的功能是:点击按钮1弹出提示框显示数字1,点击按钮2弹出提示框显示数字2,以此类推。

我该怎么做呢?

5个回答

10

请查看bind方法。

$('#button'+i).bind('click', {button: i}, function(event) {
  foo(event.data.button);
});

来自文档:

可选的eventData参数通常不使用。如果提供了此参数,该参数允许我们向处理程序传递其他信息。此参数的一个方便用途是解决由闭包引起的问题。


6

尝试这段代码:

function foo(val) {
  alert(val);
}

var funMaker = function(k) {
  return function() {
    foo(k);
  };
};

for (var i = 0; i < 3; i++) {
  $('#button'+i).click(funMaker(i));
}

这里有一些重要的要点:

  • JavaScript是函数作用域。如果你想要一个新的(更深层次的)作用域,你需要创建一个函数来保存它。
  • 这个解决方案是特定于Javascript的,它可以使用或不使用jQuery。
  • 该解决方案有效是因为i的每个值都被复制到一个新的作用域中作为k,并且从funMaker返回的函数在k周围关闭(在循环中不会改变),而不是在i周围关闭(在循环中会改变)。
  • 你的代码不能工作,因为你传递给click的函数不“拥有”i,它关闭了它的创建者的i,而那个i在循环中改变了。
  • 这个例子可以用funMaker内联写成,但我通常使用这样的辅助函数使事情更清晰。
  • funMaker的参数是k,但这没有任何区别,它可以是i而没有任何问题,因为它存在于函数funMaker的作用域中。
  • “计算机程序的构造和解释”(Sussman和Abelson)是“环境”评估模型最清晰的解释之一(http://mitpress.mit.edu/sicp/全文可在线获取,不易阅读)-请参见第3.2节。由于JavaScript实际上是具有C语法的Scheme,因此该解释是正确的。

编辑:修正了一些标点。


与darkporter的回答相同,但有一些很好的阐述。"JavaScript实际上是带有C语法的Scheme语言"。每当有人第一次理解这一点时,JS天使就会得到他的翅膀。 - Jason Orendorff

5

@Andy的解决方案是最好的。但是你也可以使用JavaScript作用域来帮助你在闭包中保存值。

你可以通过执行一个匿名函数在循环体中创建一个新的作用域来实现这一点。

for (var i = 0; i < 3; i++) {
  (function(){
    var index = i; 
    $('#button'+index).click(function(){
      foo(index);
    });
  })();
}

由于每次迭代循环体都是一个新的作用域,因此索引变量在每次迭代时都会以正确的值进行复制。


3
使用jQuery的.each函数-我猜你正在循环类似的元素-因此可以使用以下方式添加点击事件:
$(element).children(class).each(function(i){
   $(this).click(function(){
      foo(i);
   });
});

虽然没有经过测试,但我总是尽可能使用这种结构。


1

或者只需创建一个新函数,就像您所描述的那样。它会是这个样子:

function foo(val) {
    return function() {
        alert(val);
    }
}

for (var i = 0; i < 3; i++) {
    $('#button'+i).click(foo(i));
}

我非常确定Mehrdad的解决方案不起作用。当你看到人们复制到一个临时变量时,通常是为了保存“this”的值,因为它在内部子范围内可能会有所不同。

这是我所知道的最佳答案。使用bind的代码很好,但是那个数据参数真的是一个丑陋的hack。这种方法的优点是它可以在任何时候解决这个问题(创建引用循环变量的闭包),无论你是否有jQuery。 - Jason Orendorff
@Jason:虽然我认为这是一个很好的答案,如果问题没有提及并标记jQuery,那肯定是我会给出的答案。但我不同意它是一个“丑陋的hack”——它肯定更整洁。此外,在第5版规范中有一个半类似的Function.bind,因此您可以说在纯粹的js中提供该功能的最佳答案是等效的原型方法,以便在当前版本的js中提供该功能。http://snipplr.com/view/13987/functionbind/只是我能找到的一个例子,我知道有很多。 - Andy E
1
哦,数据参数让我感觉像个hack,因为它使用了C风格的“穷人闭包”来解决实际闭包的问题,这似乎有点遗憾。无论如何,它们都很好,我认为主要问题是它们共享的一个问题:如果没有注释,对于那些以前没见过这个特定问题的读者来说,为什么我们要跳过直接关闭i这个东西不够明显。:-\ - Jason Orendorff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接