我是JavaScript的初学者,我已经阅读过类似主题的每个答案,我仍然无法理解真正发生的事情,因为没有人解释我对此感到困惑的部分。
我有一个包含两个段落的HTML文档,这是我用来在点击它时将段落的颜色更改为red
的原因:
var func = function() {
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
}
func();
结果是,只要我点击,只有最后一段变为红色。所有类似于我的问题的答案都说在For
循环结束后,i
的值将为1
,这就是闭包将使用的内容,然后是{{ 1}}会被添加到同一个第二段吗?我可以使用eventListener
或Immediately-Invoked Function
来解决这个问题,以使let
成为封闭的私有,我不知道为什么如果封闭有私有i
私有在循环的每次迭代中访问它..
我只是无法理解这里到底发生了什么,是不是循环执行了一行又一个?开始时ì
的值为i
,因此在0
变量Line 4
将包含第一段,然后在p
该函数将使用那个Line 5-6
并将监听器附加到它,然后第二次执行循环,p
的值为i
,然后再次1
,闭包得到新值Line 5-6
?
我知道闭包可以像p
一样访问全局变量,因此当它的值发生变化时,它可以i
访问p
。
我在这里想念的是什么?非常感谢你提前!
答案 0 :(得分:2)
您正在展示众所周知的封闭示例......
这首先很难掌握
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
第5,6和7行包含存储在每个段落中的匿名回调函数。该函数依赖于父函数中的变量i
,因为内部函数使用p
,其定义为paragraphs[i]
。因此,即使您未在内部函数中明确使用i
,您的p
变量也是如此。让我们说文档中有7个段落...因此,您有7个函数已经关闭&#34;围绕一个i
变量。当父函数终止时,i
不能超出范围,因为7个函数需要它。因此,当人类点击其中一个段落时,循环已完成(i
现在将为8)并且每个函数都在查看相同的i
值。
要解决这个问题,点击回调函数需要各自获得自己的值,而不是共享一个。这可以通过多种方式实现,但它们都涉及将i
的副本传递到单击回调函数中,以便i
值的COPY将存储在每个单击回调函数中或删除完全使用i
。仍然会有一个闭包,但不会出现你最初遇到的副作用,因为嵌套函数不会依赖父函数中的变量。
这是一个从嵌套函数中删除i
的示例,从而解决了问题:
var paragraphs = document.querySelectorAll('p')
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].addEventListener('click', function() {
this.classList.toggle('red')
});
}
&#13;
.red {color:red;}
&#13;
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p class="red">Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
&#13;
答案 1 :(得分:1)
闭包是一个关闭变量值的函数,因此它们不会在循环的下一次迭代中发生变化,这发生在click
发生之前等等。
var paragraphs = document.querySelectorAll('p');
for (var i = 0; i < paragraphs.length; i++) {
(function(p) { // <- any function call, would create a new scope, and hence a closure
p.addEventListener('click', function() {
p.classList.toggle('red'); // note that "this" would work here, instead of "p"
});
})(paragraphs[i]); // <- in this case, it's an IIFE, but it doesn't have to be
}
这将是一个闭包
答案 2 :(得分:1)
在JavaScript(ECMA-Script 5及更早版本)中,只有函数可以创建范围。
另一方面,闭包不会捕获变量值。也就是说,你需要自己动手,因为你已经说过使用IIFE(立即调用的函数表达式):
TextView
BTW谁知道您是否可以使用for (var i = 0; i < paragraphs.length; i++) {
(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red')
})
})(paragraphs[i]);
}
简化此代码:
document.querySelectorAll
嗯,实际上,您可以重构代码以使用// Now you don't need IIFEs anymore...
// Change the "p" selector with whatever CSS selector
// that might fit better in your scenario...
Array.from(document.querySelectorAll("p"))
.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
,但您也不需要使用IIFE:
Array.prototype.forEach
答案 3 :(得分:1)
这是由变量p
绑定到变量的变量引起的经典问题,该变量的值在调用回调时已更改。
对于迭代数组并在没有任何特殊技巧的情况下调用异步代码,您只需使用Array.prototype.forEach
(在某些浏览器上,classList
属性也支持此方法):
paragraphs.forEach(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red');
});
});
由于p
是forEach
回调的绑定参数,它始终保持当前迭代的期望值 ,并且正确的元素切换。
如果您的浏览器 不支持classList.forEach
,请使用:
[].prototype.forEach.call(paragraphs, ...);