【问题标题】:Closures in a for loopfor循环中的闭包
【发布时间】:2011-01-12 15:16:23
【问题描述】:

循环中的闭包给我带来了问题。我想我必须创建另一个函数来返回一个函数来解决问题,但我无法让它与我的 jQuery 代码一起使用。

这是一个简化形式的基本问题:

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

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

点击三个按钮中的任何一个都会发出警报,说 3。我想要的功能是点击按钮 1 会发出警报,说 1,按钮 2 会说 2,依此类推。

我怎样才能做到这一点?

【问题讨论】:

    标签: javascript jquery loops closures


    【解决方案1】:

    参见bind 方法。

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

    来自文档:

    可选的 eventData 参数是 不常用。提供时,此 参数允许我们传递额外的 信息给处理者。一个方便的 使用这个参数是工作 围绕关闭引起的问题

    【讨论】:

      【解决方案2】:

      试试这个代码:

      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 天使就会长出翅膀。
      【解决方案3】:

      @Andy 解决方案是最好的。但是您也可以使用 Javascript 范围来帮助您保存闭包中的值。

      您可以通过执行匿名函数在循环体中创建一个新范围来做到这一点。

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

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

      【讨论】:

        【解决方案4】:

        使用 jquery 中的 .each 函数——我猜你是在循环遍历类似的元素——所以使用类似的东西添加点击:

        $(element).children(class).each(function(i){
           $(this).click(function(){
              foo(i);
           });
        });
        

        未经测试,但我总是尽可能使用这种结构。

        【讨论】:

          【解决方案5】:

          或者只是制造一个新功能,就像你描述的那样。它看起来像这样:

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

          我很确定 Mehrdad 的解决方案行不通。当您看到人们复制到临时变量时,通常是保存“this”的值,该值在内部子范围内可能会有所不同。

          【讨论】:

          • 这是我所知道的最好的答案。带有 bind 的代码很好,但是那个 data 参数确实是一个丑陋的 hack。这种方法的优点是它可以在任何时候遇到这个问题(创建引用循环变量的闭包),无论你是否有 jQuery。
          • @Jason:虽然我同意这是一个很好的答案,当然如果没有在问题中提及和标记 jQuery 我会给出答案,但我不得不不同意它是一个“丑陋的hack”——当然更整洁。此外,第 5 版规范中有一个半相似的Function.bind,因此您可以说普通 ol' js 的最佳答案是在当前版本的 js 中提供该功能的等效原型设计方法。 snipplr.com/view/13987/functionbind 只是我能找到的一个例子,我知道有很多。
          • 哦,data 参数让我印象深刻,因为它使用 C 风格的穷人闭包来解决实际闭包的问题。这似乎有点悲伤。无论如何,他们都很好,我认为主要问题是他们都有共同的问题:如果没有评论,对于在为什么我们之前没有看过这个特殊问题的读者来说,这永远不够明显'正在跳过这个额外的箍而不是直接关闭i。 :-\
          猜你喜欢
          • 2012-12-15
          • 2021-10-27
          • 1970-01-01
          • 1970-01-01
          • 2021-08-30
          • 2018-12-26
          • 1970-01-01
          • 1970-01-01
          • 2018-05-02
          相关资源
          最近更新 更多