循环绑定click事件监听器时仅最后一个子元素响应异常原因咨询
为什么点击评分圆圈只有最后一个会变色?
我正在制作一个评分卡片:
预期行为是点击任意评分圆圈时,该圆圈会通过切换CSS类变为灰色。但用下面的代码实现时,不管点击哪个圆圈,始终只有最后一个变为灰色;换成evt.target.classList.toggle后就能正常实现预期效果,作为前端新手想搞懂其中的原因。
有问题的代码
// 获取元素 const submitBtn = document.querySelector('button'); const ratingsParent = document.querySelector('.ratings'); console.log(ratingsParent.children); // 测试按钮点击 submitBtn.addEventListener('click', () => { console.log('Clicked'); }) // 绑定评分圆圈的点击事件 for (var r of ratingsParent.children) { r.addEventListener('click', ()=> { r.classList.toggle('clicked'); }) }
问题根源:var的作用域特性
这是典型的变量作用域与异步回调的冲突问题:
var声明的变量是函数/全局作用域,不是块级作用域。在for循环里用var r,整个循环过程中只有一个r变量存在,循环每一次迭代都会把r的值更新为当前的子元素,循环结束后r最终指向最后一个子元素。- 点击事件的回调函数是异步执行的——只有当你点击圆圈时才会触发。这时候循环已经跑完了,回调函数里读取的
r已经是最后一个元素的引用,所以不管点哪个圆圈,操作的都是最后那个元素的类。
为什么evt.target能解决问题?
evt.target是事件对象里的属性,它指向实际触发事件的DOM元素——也就是你点击的那个圆圈。直接操作这个元素的类,自然能精准对应到你点击的目标,不会受循环变量的影响。
其他两种可行的解决方法
除了用evt.target,还可以通过修正变量作用域来解决:
- 把
var换成let:let是块级作用域,每次循环都会创建一个全新的r变量,绑定到当前迭代的元素,回调函数里的r会对应各自的元素:
for (let r of ratingsParent.children) { r.addEventListener('click', ()=> { r.classList.toggle('clicked'); }) }
- 用闭包保存变量:通过立即执行函数把每次循环的
r值封闭起来,避免被后续迭代覆盖:
for (var r of ratingsParent.children) { (function(currentR) { currentR.addEventListener('click', ()=> { currentR.classList.toggle('clicked'); }) })(r); }
内容的提问来源于stack exchange,提问作者Aman Savaria
相关产品推荐
相关产品推荐

