点击事件循环丢失数组范围[重复]
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)
经典关闭问题
将它们包含在一个匿名函数中,并在上下文中分配当前迭代。这应该可以解决问题..
【讨论】:
以上是关于点击事件循环丢失数组范围[重复]的主要内容,如果未能解决你的问题,请参考以下文章