为HTMLDivElement数组添加addEventListener时的闭包问题求助
问题分析与解决方案
这是个非常经典的JavaScript作用域问题,其实跟箭头函数关系不大,核心问题出在你用var声明循环变量i的特性上~
为什么会输出3What button...?
var声明的变量是函数级作用域,也就是说整个SetUpHotSpots函数里只有一个i的引用。当循环执行时,你给每个热点绑定了点击事件的箭头函数,这个箭头函数并没有立即执行,而是在点击时才会触发。等循环结束后,i的值已经变成了this.HotSpotArray.length(也就是3),所以不管点击哪个热点,箭头函数访问的都是这个已经更新后的i值。
解决方案
这里有几种简单有效的解决方式:
1. 用let替换var(最简洁的现代方案)
let是ES6引入的块级作用域变量,每次循环都会创建一个独立的i变量,箭头函数捕获的是当前循环迭代的那个i的引用,完美解决作用域共享问题:
private SetUpHotSpots() { console.log("Entered SetupHotSpots"); for (let i = 0; i < this.HotSpotArray.length; i++){ console.log("Entered Loop: " + i); this.HotSpotArray[i].addEventListener("click", () => { console.log(i + "What button did we click on"); }); } }
2. 使用forEach遍历数组
数组的forEach方法会为每个元素创建独立的作用域,回调函数里的索引参数是每个元素专属的,也能避免作用域冲突:
private SetUpHotSpots() { console.log("Entered SetupHotSpots"); this.HotSpotArray.forEach((hotSpot, i) => { console.log("Entered Loop: " + i); hotSpot.addEventListener("click", () => { console.log(i + "What button did we click on"); }); }); }
3. 闭包包裹(兼容旧环境的ES5方案)
如果你的运行环境不支持ES6,还可以用立即执行函数(IIFE)创建闭包,把当前循环的i值传递进去,形成独立的作用域:
private SetUpHotSpots() { console.log("Entered SetupHotSpots"); for (var i = 0; i < this.HotSpotArray.length; i++){ console.log("Entered Loop: " + i); // 用立即执行函数捕获当前的i值 (function(currentIndex) { this.HotSpotArray[currentIndex].addEventListener("click", function() { console.log(currentIndex + "What button did we click on"); }); }).call(this, i); // 用call绑定this,确保能访问到HotSpotArray } }
总结
推荐优先使用let或者forEach的方案,代码更简洁易读,也是现代JavaScript的标准写法。
内容的提问来源于stack exchange,提问作者Dylan Anlezark
相关产品推荐
相关产品推荐

