我试图弄清楚为什么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循环如何工作?感谢您的帮助。
答案 0 :(得分:1)
这与如何将var
悬挂在事件侦听器之外并在循环期间进行重新定义有关,但不保留在事件侦听器的范围之内。更改为现代的const
和let
变量声明将解决您的问题。
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>
如果您的安装足够旧而不允许使用let
或const
,则可以尝试以下方法:
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搜索,测试和编写这个答案。它着重于寻找如何以及为什么,最终与hoisting和closures有关。在写这篇文章的过程中,我的意思是“操,我还不了解JS,也许我要给出的答案也不是正确的答案”。如果是这样,请纠正我。
您可以通过添加这三个console.log
来诊断问题,并查看var
和let
之间的不同行为。
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
let
Uncaught ReferenceError: i is not defined
页面首次加载时.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
,事件处理程序的i
与i
的{{1}}在相同的引用中(意味着它们是RAM上的相同内存块) ,并且始终保持连接状态,并且在中断循环后,window.i
被增加到i
。因此,在循环之后,1
为1,函数中的window.i
也为1。另一方面,对于i
,let
的范围为for循环(因为let具有块作用域),所以它在循环结束后就死了,在被i
引用之前,它与事件处理程序i
中的els[i]
断开连接,之后增量后的新值更新为els[i]
,因此在处理程序上下文中的els[i]
现在与值为1的i隔离。这就是为什么我用let获得正确值的原因。
但是,顺便说一句,函数处理程序的i
仍然有效,如果尝试在处理程序中更改i
的值,您会发现它下次会影响该值调用。