通过getElementsByClassName添加事件监听器时为何仅触发panel4?
问题原因与解决方案
问题根源
你遇到的问题是变量作用域导致的闭包陷阱:循环中用var声明的targetPanel是全局作用域变量,循环结束后它会指向最后一个面板(panel4)。所有点击事件的回调函数都是闭包,引用的是同一个targetPanel变量,所以无论点击哪个面板,最终操作的都是最后一个。
另外原代码里document.getElementById(plbelements[l].id)是冗余操作,plbelements[l]已经是目标DOM元素,不需要再通过ID获取。
解决方案1:使用let声明变量(推荐)
let是ES6新增的块级作用域变量,每次循环会创建独立的变量绑定,每个事件回调会引用当前循环的targetPanel:
const plbelements = document.getElementsByClassName("panel"); const actionSwipeAmount = "160px"; // 常量提取到循环外,避免重复定义 for (let l = 0; l < plbelements.length; l++) { const targetPanel = plbelements[l]; console.log("Target Panel before event listener = " + targetPanel.id); targetPanel.addEventListener("click", () => { const left = window.getComputedStyle(targetPanel).getPropertyValue("left"); console.log(targetPanel.id); targetPanel.style.left = left !== actionSwipeAmount ? actionSwipeAmount : "0px"; }); }
解决方案2:立即执行函数(IIFE)创建闭包
如果需要兼容ES5环境,可以用立即执行函数把每个循环的面板变量封装到独立作用域中:
var plbelements = document.getElementsByClassName("panel"); var actionSwipeAmount = "160px"; for (var l = 0; l < plbelements.length; l++) { var targetPanel = plbelements[l]; console.log("Target Panel before event listener = " + targetPanel.id); // 用IIFE保存当前循环的panel引用 (function(panel) { panel.addEventListener("click", function() { var left = window.getComputedStyle(panel).getPropertyValue("left"); console.log(panel.id); if (left != actionSwipeAmount) { panel.style.left = actionSwipeAmount; } else { panel.style.left = "0px"; } }); })(targetPanel); }
解决方案3:使用forEach遍历
把HTMLCollection转为数组后用forEach遍历,forEach的回调函数会自动创建独立作用域:
const plbelements = Array.from(document.getElementsByClassName("panel")); const actionSwipeAmount = "160px"; plbelements.forEach(panel => { console.log("Target Panel before event listener = " + panel.id); panel.addEventListener("click", () => { const left = window.getComputedStyle(panel).getPropertyValue("left"); console.log(panel.id); panel.style.left = left !== actionSwipeAmount ? actionSwipeAmount : "0px"; }); });
内容的提问来源于stack exchange,提问作者Glen Keybit
相关产品推荐
相关产品推荐

