在 Javascript 中的 for 循环中关闭,结果令人困惑
Closure inside a for Loop in Javascript with confusing results
我是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
一样访问全局变量,所以当它的值改变时它可以访问 Line 4
处的 p
。
请问我在这里遗漏了什么?非常感谢您!
闭包是一个关闭变量值的函数,因此它们不会在循环的下一次迭代中发生变化,这发生在 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
}
那将是一个闭包
在 JavaScript(ECMA-Script 5 及更早版本)中,只有 函数 可以创建作用域。
另一方面,闭包不捕获变量值。也就是说,你需要自己做,正如你已经说过的使用 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');
});
});
这是一个经典问题,由您的变量 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, ...);
您展示的是众所周知的闭包示例....
一开始可能很难理解
/*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 个函数 "closed" 围绕一个 i
变量。 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>
我是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
一样访问全局变量,所以当它的值改变时它可以访问 Line 4
处的 p
。
请问我在这里遗漏了什么?非常感谢您!
闭包是一个关闭变量值的函数,因此它们不会在循环的下一次迭代中发生变化,这发生在 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
}
那将是一个闭包
在 JavaScript(ECMA-Script 5 及更早版本)中,只有 函数 可以创建作用域。
另一方面,闭包不捕获变量值。也就是说,你需要自己做,正如你已经说过的使用 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');
});
});
这是一个经典问题,由您的变量 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, ...);
您展示的是众所周知的闭包示例....
一开始可能很难理解
/*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 个函数 "closed" 围绕一个 i
变量。 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>