动态生成按钮的EventHandler为何仅获取数组最后一项?
解决循环中事件监听器仅获取最后一项的问题
这个问题是JavaScript里经典的作用域绑定坑,我来帮你拆解一下原因和解决办法:
为什么会出现这个问题?
你用var声明的i和site是函数级作用域的变量,整个循环过程中它们都是同一个引用。当循环结束后,i的值变成了数组的长度,site也变成了数组的最后一项。所有的点击事件回调函数都引用着这同一个变量,所以不管你点哪个按钮,都会触发最后一项的操作。
几种可行的修改方案
方案1:用let替换var(ES6+推荐方案)
let是块级作用域的变量,每次循环迭代都会创建一个新的变量绑定,这样每个回调函数都会拿到当前循环对应的site值:
for (let i=0; i<length; i++) { console.log(i); let row = document.createElement("tr"); let site = array[i]; console.log(site); // 创建WEBSITE单元格的代码不变 var cellWebsite = document.createElement("td"); var website = document.createTextNode(array[i]); cellWebsite.appendChild(website); row.appendChild(cellWebsite); // 创建删除按钮单元格的代码不变 var cellDelete = document.createElement("td"); var button = document.createElement("BUTTON"); var text = document.createTextNode("x"); button.appendChild(text); cellDelete.appendChild(button); row.appendChild(cellDelete); // 事件监听部分 button.addEventListener('click', function(event) { deleteSite(site); console.log("deleted : " + site); }); }
方案2:使用立即执行函数(IIFE)创建闭包
如果你的环境不支持ES6,可以用闭包把当前迭代的site值保存下来:
for (var i=0; i<length; i++) { console.log(i); var row = document.createElement("tr"); var site = array[i]; console.log(site); // ... 中间创建单元格和按钮的代码不变 ... // 修改事件监听为IIFE形式 button.addEventListener('click', (function(currentSite) { return function(event) { deleteSite(currentSite); console.log("deleted : " + currentSite); } })(site)); }
IIFE会立即执行,把当前的site作为参数传入,返回的回调函数会引用这个独立的currentSite变量,不会受循环结束后site值的影响。
方案3:改用forEach遍历数组
forEach的回调函数自带独立作用域,每个迭代的site都是当前项的引用,代码更简洁:
array.forEach(function(site) { console.log(site); var row = document.createElement("tr"); // ... 创建单元格和按钮的代码不变 ... button.addEventListener('click', function(event) { deleteSite(site); console.log("deleted : " + site); }); });
方案4:把数据存在按钮的dataset中
还可以直接将站点信息绑定到按钮元素上,点击时从元素本身获取数据,不依赖作用域:
for (var i=0; i<length; i++) { console.log(i); var row = document.createElement("tr"); var site = array[i]; console.log(site); // ... 中间创建单元格和按钮的代码不变 ... // 给按钮添加自定义数据属性 button.dataset.site = site; // 事件监听时从按钮获取数据 button.addEventListener('click', function(event) { const currentSite = this.dataset.site; deleteSite(currentSite); console.log("deleted : " + currentSite); }); }
总结
如果你的项目已经支持ES6+,优先选择方案1或方案3,代码更简洁易读;如果需要兼容旧环境,方案2或方案4都是可靠的选择。
内容的提问来源于stack exchange,提问作者Yoshi Hilke
相关产品推荐
相关产品推荐

