16×16网格按钮悬停变色:event.target与循环变量i为何表现不同?
为什么循环变量i和event.target在事件监听中表现不同?
这本质是JavaScript中变量作用域和异步事件触发时机的问题,两者的作用逻辑完全不一样:
1. 循环变量i的问题:共享变量的滞后绑定
如果你的循环用var声明i,i是函数级作用域的变量——整个循环里只有一个i,所有事件回调引用的都是同一个变量。
事件监听是异步触发的:循环会在瞬间执行完,而mouseover事件要等你鼠标悬停才会触发。这时候i的值已经变成了循环的最终值(比如16×16网格的最后一个按钮索引255),所以不管你悬停哪个按钮,回调都会修改buttons[i]也就是最后一个按钮的样式。
错误示例:
const buttons = document.querySelectorAll('.grid-btn'); // 用var声明i,函数级作用域 for (var i = 0; i < buttons.length; i++) { buttons[i].addEventListener('mouseover', function() { // 这里的i是循环结束后的最终值 buttons[i].style.backgroundColor = 'red'; }); }
2. event.target的本质:直接指向触发事件的元素
event.target是事件对象自带的属性,它的作用就是精准指向触发当前事件的DOM元素——不管什么时候触发事件,它都直接对应你鼠标悬停的那个按钮,所以修改它的背景色自然会正确生效。
正确示例:
const buttons = document.querySelectorAll('.grid-btn'); for (let i = 0; i < buttons.length; i++) { buttons[i].addEventListener('mouseover', function(event) { // event.target就是你悬停的那个按钮 event.target.style.backgroundColor = 'red'; }); }
解决循环变量i问题的几种方法
如果你想继续用循环变量实现,有几个常见方案:
- 用let声明i:利用块级作用域,每次迭代创建独立的i绑定
for (let i = 0; i < buttons.length; i++) { buttons[i].addEventListener('mouseover', function() { buttons[i].style.backgroundColor = 'red'; }); } - 用forEach遍历:forEach的回调参数自带独立的元素引用
buttons.forEach(button => { button.addEventListener('mouseover', () => { button.style.backgroundColor = 'red'; }); }); - 立即执行函数(IIFE)创建闭包:手动把当前i的值绑定到回调的作用域中
for (var i = 0; i < buttons.length; i++) { // 把当前i作为参数传入IIFE,创建独立作用域 (function(currentIndex) { buttons[currentIndex].addEventListener('mouseover', function() { buttons[currentIndex].style.backgroundColor = 'red'; }); })(i); }
总结一下:i是循环中的共享变量,它的值在事件触发时已经是循环结束后的最终值;而event.target是直接关联触发事件的元素,这就是两者执行结果不同的核心原因。
内容的提问来源于stack exchange,提问作者FirstTree
相关产品推荐
相关产品推荐

