首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Javascript中for循环中的闭包,结果令人困惑

Javascript中for循环中的闭包,结果令人困惑
EN

Stack Overflow用户
提问于 2016-11-02 20:59:01
回答 4查看 67关注 0票数 0

我是JavaScript的初学者,我读过类似主题的每一个答案,我仍然不明白到底发生了什么,因为没有人解释我困惑的部分。

我有一个包含两个段落的HTML文档,当我单击它时,我使用它将段落的颜色更改为red

代码语言:javascript
运行
复制
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 Functionlet来将i私有于闭包,我不知道如果闭包在循环的每一次迭代中都有访问权限,为什么要使ì成为私有的。

我只是不明白这里到底发生了什么,循环不是一个接一个地执行的吗?在开始时,i将有一个值0,因此在Line 4变量p将有第一段,然后在Line 5-6,函数将使用该p并将侦听器附加到它,然后循环第二次执行,i将有一个值1,然后在Line 5-6,闭包再次得到p的新值?

我知道闭包在这里可以访问全局变量,比如i,所以当它的值发生变化时,它可以访问p at Line 4

我在这里少了什么?非常感谢您提前!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2016-11-02 21:07:27

你展示的是谚语的闭包例子..。

这在一开始很难理解。

代码语言:javascript
运行
复制
/*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的示例,从而解决了这个问题:

代码语言:javascript
运行
复制
var paragraphs = document.querySelectorAll('p')

for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].addEventListener('click', function() {
      this.classList.toggle('red')
    });
}
代码语言:javascript
运行
复制
.red {color:red;}
代码语言:javascript
运行
复制
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p class="red">Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>

票数 2
EN

Stack Overflow用户

发布于 2016-11-02 21:01:24

闭包是在变量的值中关闭的函数,因此它们在循环的下一次迭代中不会发生变化,这发生在click发生之前,等等。

代码语言:javascript
运行
复制
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

}

那就结束了

票数 1
EN

Stack Overflow用户

发布于 2016-11-02 21:04:39

在JavaScript (ECMA-Script5和更早版本)中,只有函数才能创建作用域。

另一方面,闭包不捕获变量值。也就是说,正如您已经说过的那样,您需要自己使用IIFE(立即调用的函数表达式):

代码语言:javascript
运行
复制
for (var i = 0; i < paragraphs.length; i++) {
  (function(p) {
    p.addEventListener('click', function() {
      p.classList.toggle('red')
    })
  })(paragraphs[i]);
}

顺便说一句,谁知道您是否可以使用document.querySelectorAll简化这段代码

代码语言:javascript
运行
复制
// 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:

代码语言:javascript
运行
复制
paragraphs.forEach(function(p) {
    p.addEventListener("click", function() {
      p.classList.toggle('red');
    });
});
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40389682

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档