简单的for循环点击事件与for循环不同

时间:2019-12-16 16:23:35

标签: javascript

我试图弄清楚为什么for循环标准在这种情况下不起作用,但是for循环有效。简单的for循环的问题在于,如果打开控制台,它将返回未定义的类列表错误,因此不会将'open'类添加到div中-请参见下面的两个代码段:

可用于循环的代码:

if (document.querySelectorAll('.wrapper').length) {
  var els = document.querySelectorAll('.wrapper');

  for (var el of els) {
    var toggler = el.querySelector('a');

    toggler.addEventListener('click', function(e) {
      e.preventDefault();
      console.log(e.target);
      el.classList.toggle('open');
      e.target.classList.toggle('open');
    });
  }
}
<div class='wrapper'>
  <a href='#'>click me!</a>
</div>

带有标准for循环的代码(不起作用):

if (document.querySelectorAll('.wrapper').length) {
  var els = document.querySelectorAll('.wrapper');

  for (var i = 0; i < els.length; i++) {
    var toggler = els[i].querySelector('a');

    toggler.addEventListener('click', function(e) {
      e.preventDefault();
      console.log(e.target);
      els[i].classList.toggle('open');
      e.target.classList.toggle('open');
    });
  }
}
<div class='wrapper'>
  <a href='#'>click me!</a>
</div>

有人可以解释为什么标准for循环失败而for循环起作用吗?使用标准的for循环如何工作?感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

这与如何将var悬挂在事件侦听器之外并在循环期间进行重新定义有关,但不保留在事件侦听器的范围之内。更改为现代的constlet变量声明将解决您的问题。

const els = document.querySelectorAll('.wrapper');

if (els.length) {
  for (let i = 0; i < els.length; i++) {
    const toggler = els[i].querySelector('a');

    toggler.addEventListener('click', function(e) {
      e.preventDefault();
      console.log(e.target);
      els[i].classList.toggle('open');
      e.target.classList.toggle('open');
    });
  }
}
<div class='wrapper'>
  <a href='#'>click me!</a>
</div>


如果您的安装足够旧而不允许使用letconst,则可以尝试以下方法:

var els = document.querySelectorAll('.wrapper');
if (els.length) {
  for (var i = 0; i < els.length; i++) {
    var el = els[i]; // Re-declare `el` inside the loop
    var toggler = el.querySelector('a');

    toggler.addEventListener('click', function(e) {
      e.preventDefault();
      console.log(e.target);
      el.classList.toggle('open');
      e.target.classList.toggle('open');
    });
  }
}
<div class='wrapper'>
  <a href='#'>click me!</a>
</div>

答案 1 :(得分:0)

我知道这里有一个可以接受的答案,但是无论如何都会给出我的见解,我花了一个多小时才去Google搜索,测试和编写这个答案。它着重于寻找如何以及为什么,最终与hoistingclosures有关。在写这篇文章的过程中,我的意思是“操,我还不了解JS,也许我要给出的答案也不是正确的答案”。如果是这样,请纠正我。


您可以通过添加这三个console.log来诊断问题,并查看varlet之间的不同行为。

if (document.querySelectorAll('.wrapper').length) {
  var els = document.querySelectorAll('.wrapper');

  for (var/*alternate with let*/ i = 0; i < els.length; i++) {
        var toggler = els[i].querySelector('a');

        toggler.addEventListener('click', function(e) {
            e.preventDefault();
            console.log(e.target);
            console.log('i', i) // var: 1, let: 0
            console.log('els[0]', els[0]) // both are the element `.wrapper a`
            els[i].classList.toggle('open');
            e.target.classList.toggle('open');
        });
  }
}
console.log('load', i) // var: 1, let: ReferenceError

结果将是:

var

  • 页面第一次加载时记录
  • 每次点击记录1次

let

  • Uncaught ReferenceError: i is not defined页面首次加载时
  • 每次点击记录0,并按预期记录您的.wrapper a项目。

在两种情况下,els[0]是有效项目。


现在连接点,您得到Cannot read property 'classList' of undefined的意思是.classList els[i]undefined之前的位置。但是els[0]不是undefined,因此未定义的els[1]undefined,因为els只有1个元素els [0]。

为什么,为什么在var的情况下,i是1,而在let的情况下,i是0?并且当在底部引用它时,var i为1,但是let时,您不能引用它(参考错误)。称为hoisting,为了更好地理解与本文最相关的特定示例,您可以看一下this的最后一个示例。

如果您在console.log(window.i)之后的底部添加另一个console.log('load', i),则会看到i等于window.i,并且i仍然有效整个时间。因此,当您在事件处理程序中调用els[i]时,i引用引用i的函数上下文中的toggler.addEventListener('click', function(e) {,这与另一个称为closures的术语。对于var,事件处理程序的ii的{​​{1}}在相同的引用中(意味着它们是RAM上的相同内存块) ,并且始终保持连接状态,并且在中断循环后,window.i被增加到i。因此,在循环之后,1为1,函数中的window.i也为1。另一方面,对于ilet的范围为for循环(因为let具有块作用域),所以它在循环结束后就死了,在被i引用之前,它与事件处理程序i中的els[i]断开连接,之后增量后的新值更新为els[i],因此在处理程序上下文中的els[i]现在与值为1的i隔离。这就是为什么我用let获得正确值的原因。

但是,顺便说一句,函数处理程序的i仍然有效,如果尝试在处理程序中更改i的值,您会发现它下次会影响该值调用。