【问题标题】:Losing Scope of Array on Click Event Loop [duplicate]点击事件循环丢失数组范围[重复]
【发布时间】:2012-11-16 23:39:15
【问题描述】:

可能重复:
Javascript closure inside loops - simple practical example

我有一个包含 4 个对象 (that.pairs) 的数组,每个对象都有一个 .t 属性,它是一个 jQuery 对象/元素。我正在尝试在每个被点击的t 上设置一个事件。

问题在于,当其中一个被点击时,总是最后一对(索引 3)被传递到我的 doToggle() 函数中。

为什么会这样?我该如何解决?

for (var i = 0; i < that.pairs.length; i++) {
    var p = that.pairs[i];
    p.t.click(function() {
        that.doToggle(p);
    });
}

【问题讨论】:

  • 我认为这个问题不值得投反对票,它已经得到了很好的解释。我知道这已经被问了一百万次了,但是你怎么知道要搜索什么?
  • @JuanMendes:嗯,我不知道。这个问题被问了很多次,任何对问题的通用措辞都可能产生正确的答案。例如,“javascript 事件循环函数总是最后一对”“javascript 循环总是最后一项”“javascript 回调最后一个元素”“javascript 循环相同的值”——哎呀,甚至只是“javascript 循环问题”。在搜索中很难找到答案。
  • @Chuck mmm... 我搜索了Losing Scope of Array on Click Event Loop 并且第三个结果是相关的...所以你确实有道理,但它并不像你声称的那么明显。如果他们对关闭一无所知,我仍然不确定 OP 是否能够弄清楚。我保留对严重问题的反对意见,例如不包含任何代码、不显示任何努力、不显示错误消息、说“它不起作用”......

标签: javascript jquery arrays for-loop dom-events


【解决方案1】:

这是因为 p 变量由您的闭包共享,因此只有一个 p 变量。当你的处理程序被调用时,p 已经改变了。

你必须使用我称之为冻结你的闭包的技术

for (var i = 0; i < that.pairs.length; i++) {
    // The extra function call creates a separate closure for each
    // iteration of the loop
    (function(p){
        p.t.click(function() {
            that.doToggle(p);
        });
    })(that.pairs[i]); //passing the variable to freeze, creating a new closure
}

以下是更容易理解的方法

function createHandler(that, p) {
    return function() {
       that.doToggle(p);
    }
}

for (var i = 0; i < that.pairs.length; i++) {
    var p = that.pairs[i];
    // Because we're calling a function that returns the handler
    // a new closure is created that keeps the current value of that and p
    p.t.click(createHandler(that, p));
}

闭包优化

由于在 cmets 中有很多关于闭包的讨论,我决定放这两个屏幕截图,显示闭包得到了优化,并且只包含了所需的变量

此示例 http://jsfiddle.net/TnGxJ/2/ 显示如何仅包含 a

在这个例子中http://jsfiddle.net/TnGxJ/1/,因为有一个eval,所以所有的变量都被括起来了。

【讨论】:

  • JavaScript 中的每个函数都将其执行上下文绑定到其外部执行上下文。这使它成为一个闭包,无论您是否实际引用可用变量。所有 JavaScript 函数都是闭包。话虽如此,胡安是对的。有 IIFE (外部) 的执行上下文,然后是处理程序的每次调用的执行上下文。每个处理程序调用的上下文都永久绑定到 IIFE 的上下文。
  • @zerkms:这只是无效的语法,但如果你给它一个名字,它的执行上下文就会绑定到全局执行上下文。
  • JuanMendes:不属于表达式的函数需要名称,因此function(){} 给出了 SyntaxError。
  • @user1689607 如果您实际查看 google chrome 的闭包堆栈,您会注意到一个变量仅可用于检查它是否已在闭包中使用,因此编译器实际上优化了闭包。如果函数包含eval,则所有变量都包含在内,因为编译器不能保证只需要一些变量。请参阅我的答案中的屏幕截图
【解决方案2】:

使用$.each 而不是for 循环,这样每次迭代都会获得一个新的变量范围。

$.each(that.pairs, function(i, p) {
    p.t.click(function() {
        that.doToggle(p);
    });
});

这样每个click 处理程序都会关闭一个唯一的变量范围,而不是共享的外部变量范围。

【讨论】:

  • 它解决了这个问题,但你应该告诉 OP 发生了什么以及为什么只对最后一个项目采取行动
【解决方案3】:
for (var i = 0; i < that.pairs.length; i++) {
    var p = that.pairs[i];
    (function(p){
        p.t.click(function() {
            that.doToggle(p);
        });
    }(p));
}

这个使用 IIFE 的技巧可以解决您现在遇到的关闭“问题”。

【讨论】:

    【解决方案4】:
    for (var i = 0; i < that.pairs.length; i++) {
        (function(num){
           var p = that.pairs[num];
           p.t.click(function() {
              that.doToggle(p);
           });
        })(i)
    }
    

    经典关闭问题

    将它们包含在一个匿名函数中,并在上下文中分配当前迭代。这应该可以解决问题..

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-25
      • 2018-11-04
      相关资源
      最近更新 更多