点击事件循环丢失数组范围[重复]

Posted

技术标签:

【中文标题】点击事件循环丢失数组范围[重复]【英文标题】: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 是否能够弄清楚。我保留对严重问题的反对意见,例如不包含任何代码、不显示任何努力、不显示错误消息、说“它不起作用”...... 【参考方案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)

经典关闭问题

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

【讨论】:

以上是关于点击事件循环丢失数组范围[重复]的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript for循环不重复绑定点击事件

一段时间后,点击事件就会丢失

微信小程序怎么靠点击事件拿到对应数组的唯一id?

for循环,绑定点击事件,二维数组列表渲染

JS for循环遍历点击事件

如何在 UITextView 中处理点击而不丢失默认点击句柄事件