JavaScript:循环创建的事件监听器如何使用变量实际值
解决循环中事件监听器捕获变量引用的问题
这绝对是JavaScript里最经典的闭包陷阱之一,我当初刚学JS的时候也踩过一模一样的坑!
问题原因
你用var声明的index是函数级作用域的变量——整个循环里只有这一个index的引用。当你给每个按钮绑定mouseover事件时,事件处理函数并没有立刻执行,它们只是“记住”了要访问这个index变量。等你鼠标悬停触发事件的时候,循环早就跑完了,index已经变成了最终值5(因为hello拆成5个字符,循环5次,index从0加到5),所以每个按钮都会alert出5。
三种解决方案
1. 用let替代var(最推荐,ES6+写法)
let是块级作用域,每次循环都会创建一个全新的index变量,每个事件处理函数捕获的都是当前循环对应的那个index值:
const word = 'hello'.split('') // 把index用let声明在循环里,每次循环都有独立的作用域 for (let index = 0; index < word.length; index++) { const button = document.createElement('button') button.innerText = word[index] button.addEventListener('mouseover', () => alert(index)) document.body.appendChild(button) }
或者在你的for...of循环里,把当前的index存成一个块级作用域的变量:
const word = 'hello'.split('') let index = 0 for (const letter of word) { const button = document.createElement('button') button.innerText = letter // 用let创建当前循环的独立变量 let currentIndex = index button.addEventListener('mouseover', () => alert(currentIndex)) index++ document.body.appendChild(button) }
2. 立即执行函数(IIFE)兼容旧环境
如果需要兼容ES6之前的环境,可以用立即执行函数给每个事件处理函数创建独立作用域,把当前的index值传进去:
var word = 'hello'.split('') var index = 0 for (letter of word) { var button = document.createElement('button') button.innerText = letter // 用IIFE捕获当前循环的index值 (function(currentIndex) { button.addEventListener('mouseover', function() { alert(currentIndex) }) })(index) index++ document.body.appendChild(button) }
3. 利用自定义属性存储值
还可以把索引值存在按钮的自定义属性里,触发事件时直接读取这个属性:
var word = 'hello'.split('') var index = 0 for (letter of word) { var button = document.createElement('button') button.innerText = letter // 把index存在按钮的dataset属性里 button.dataset.index = index button.addEventListener('mouseover', function() { alert(this.dataset.index) }) index++ document.body.appendChild(button) }
总结
最简洁优雅的方式就是用let声明块级作用域变量,这也是ES6之后官方推荐的写法。如果要兼容老项目,IIFE或者自定义属性的方式也能完美解决问题。
内容的提问来源于stack exchange,提问作者therks
相关产品推荐
相关产品推荐

