使用for...of循环为所有按钮添加事件监听器的异常问题排查
问题成因
问题核心是变量作用域和闭包的共同作用:
- 循环变量
b没有使用let/const声明,属于隐式创建的全局变量,整个循环过程只会复用同一个b变量,每次迭代都会覆盖它的值 - 点击事件的回调函数是异步执行的,触发回调时循环早已执行完毕,此时
b的值已经被覆盖为最后一个按钮元素,所以所有回调操作的都是最后一个按钮
修复方案
有两种成熟的修复方式:
方案1:给迭代变量添加块级作用域声明
在for...of循环里用let声明b,每次迭代都会创建独立的块级作用域绑定,每个回调捕获的都是当前迭代对应的b变量:
var buttons = document.getElementsByTagName("button"); // 为迭代变量添加let声明 for (let b of buttons) { b.addEventListener("click", function() { var n = document.createElement("span"); n.innerHTML = "new"; b.parentElement.appendChild(n); b.style.display = "none"; }); }
方案2:在回调中使用this指向触发事件的元素
事件回调函数的this默认指向绑定事件的元素,不需要依赖外部的循环变量,规避作用域问题:
var buttons = document.getElementsByTagName("button"); for (b of buttons) { b.addEventListener("click", function() { var n = document.createElement("span"); n.innerHTML = "new"; // 用this代替外部循环变量b this.parentElement.appendChild(n); this.style.display = "none"; }); }
for...of的适用性 for...of完全适合这类批量绑定事件的需求:
- 它天生支持遍历
HTMLCollection、NodeList这类DOM集合对象,语法比传统for循环更简洁易读 - 你遇到的问题是代码写法不规范导致的,和
for...of本身无关,只要注意正确声明迭代变量的作用域(用let/const而不是隐式全局变量),就可以正常使用
内容的提问来源于stack exchange,提问作者AbgrundLemur
相关产品推荐
相关产品推荐

