循环绑定事件监听器时如何为每个元素传入唯一索引参数
问题根本原因
你遇到的是经典的JavaScript循环中异步回调的变量共享问题:
- 你代码里的循环变量
i没有用块级作用域声明,属于全局变量,整个循环全程只会共用同一个i的内存地址 - 当你点击按钮触发回调时,循环早已执行完毕,此时
i的值已经固定为document.getElementsByClassName("CTAenabled").length,所有回调读取的都是同一个最终值,所以打印结果全部相同。
解决方法
方法1:用let声明循环变量(最简单,ES6+支持)
ES6中let声明的变量在for循环里会为每一次迭代创建独立的块级作用域绑定,每个回调引用的都是当前迭代对应的i值:
let CTAs = [] // 仅修改此处,给i加let声明 for(let i = 0; i < document.getElementsByClassName("CTAenabled").length; i++) { CTAs.push(document.getElementsByClassName("CTAenabled")[i]) CTAs[i].addEventListener("click", () => {myFunction(i)}) } function myFunction(n) { console.log(n) }
方法2:用forEach遍历(代码更简洁,推荐)
直接把获取到的DOM集合转成数组后用forEach遍历,迭代回调的索引参数天然是每次独立的:
// 把类数组转成真实数组,直接遍历 const CTAs = Array.from(document.getElementsByClassName("CTAenabled")) CTAs.forEach((btn, index) => { btn.addEventListener("click", () => myFunction(index)) }) function myFunction(n) { console.log(n) }
方法3:闭包封装(兼容旧版ES5环境)
如果需要兼容不支持ES6的老旧环境,可以用立即执行函数(IIFE)把每次迭代的i值封存到独立的函数作用域里:
let CTAs = [] for(var i = 0; i < document.getElementsByClassName("CTAenabled").length; i++) { CTAs.push(document.getElementsByClassName("CTAenabled")[i]) // 用IIFE把当前i作为参数传入,封存当前值 (function(currentIndex) { CTAs[i].addEventListener("click", () => {myFunction(currentIndex)}) })(i) } function myFunction(n) { console.log(n) }
方法4:事件委托(适合按钮动态增减的场景)
不用给每个按钮单独绑定监听,直接给公共父元素绑定事件,触发时动态计算索引,性能更高也不用处理新增/删除按钮的绑定解绑:
// 假设所有CTAenabled按钮的父元素id是ctaContainer document.querySelector('#ctaContainer').addEventListener('click', (e) => { if(e.target.classList.contains('CTAenabled')) { // 计算当前点击按钮的索引 const allBtns = Array.from(document.getElementsByClassName("CTAenabled")) const index = allBtns.indexOf(e.target) myFunction(index) } }) function myFunction(n) { console.log(n) }
内容的提问来源于stack exchange,提问作者Víctor Leal
相关产品推荐
相关产品推荐

