我是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
,所以这就是闭包所使用的,然后eventListener
将被添加到同一第二段中?而且我可以使用Immediately-Invoked Function
或let
来将i
私有于闭包,我不知道如果闭包在循环的每一次迭代中都有访问权限,为什么要使ì
成为私有的。
我只是不明白这里到底发生了什么,循环不是一个接一个地执行的吗?在开始时,i
将有一个值0
,因此在Line 4
变量p
将有第一段,然后在Line 5-6
,函数将使用该p
并将侦听器附加到它,然后循环第二次执行,i
将有一个值1
,然后在Line 5-6
,闭包再次得到p
的新值?
我知道闭包在这里可以访问全局变量,比如i
,所以当它的值发生变化时,它可以访问p
at Line 4
。
我在这里少了什么?非常感谢您提前!
发布于 2016-11-02 21:07:27
你展示的是谚语的闭包例子..。
这在一开始很难理解。
/*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
,因为内部函数使用定义为paragraphs[i]
的p
。因此,即使在内部函数中没有显式地使用i
,但p
变量是。假设文件中有7段。正因为如此,在一个i
变量周围有7个“封闭”函数。当父函数终止时,i
不能超出作用域,因为这7个函数需要它。因此,当人工单击其中一个段落时,循环已经完成(i
现在为8),每个函数都在查看相同的i
值。
为了解决这个问题,单击回调函数需要每个函数都得到自己的值,而不是共享一个值。这可以通过多种方式实现,但它们都涉及将i
的副本传递到单击回调函数中,以便将i
值的副本存储在每个单击回调函数中,或者一起删除i
的使用。仍然会有闭包,但是不会出现最初遇到的副作用,因为嵌套函数不会依赖父函数的变量。
下面是一个从嵌套函数中删除i
的示例,从而解决了这个问题:
var paragraphs = document.querySelectorAll('p')
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].addEventListener('click', function() {
this.classList.toggle('red')
});
}
.red {color:red;}
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p class="red">Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
发布于 2016-11-02 21:01:24
闭包是在变量的值中关闭的函数,因此它们在循环的下一次迭代中不会发生变化,这发生在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
}
那就结束了
发布于 2016-11-02 21:04:39
在JavaScript (ECMA-Script5和更早版本)中,只有函数才能创建作用域。
另一方面,闭包不捕获变量值。也就是说,正如您已经说过的那样,您需要自己使用IIFE(立即调用的函数表达式):
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');
});
});
实际上,您可以重构代码以使用Array.prototype.forEach
,也不需要使用IIFE:
paragraphs.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
https://stackoverflow.com/questions/40389682
复制相似问题