For-of循环导致鼠标悬停事件首次失效问题求助
问题:鼠标悬停事件首次触发无响应,第二次才生效的原因及解决方法
正常运行的代码示例
JavaScript
let allDivs = [...document.querySelectorAll(`div`)]; let Classes = [ "A0", "A1", "A2", "A3" ]; console.log(`let 'allDivs' equals:`); console.log(allDivs); for (let i = 0; i < Classes.length; i++) { let allDivsFiltered = allDivs.filter(item => (item.className.match(`A${i}`))); console.log('---- ---- ---- ----'); console.log(`let 'allDivsFiltered [${i}]' equals:`); console.log(allDivsFiltered); for (let all of allDivsFiltered) { all.onmouseover = () => { for (let a = 0; a < allDivsFiltered.length; a++) { allDivsFiltered[a].classList.add(`B${i}`); }; }; console.log(all); }; };
CSS
body { display: flex; flex-wrap: wrap; } div { position: relative; background-color: slategray; width: 100px; height: 100px; top: 0px; left: 0px; margin-top: 4px; margin-left: 4px; } .B0 { background-color: red; } .B1 { background-color: blue; } .B2 { background-color: green; } .B3 { background-color: yellow; }
HTML
<!DOCTYPE html> <html lang="en"> <head> <title>Document</title> <link rel="stylesheet" href="a.css"> </head> <body> <div class="A0"></div> <div class="A1"></div> <div class="A2"></div> <div class="A1"></div> <div class="A2"></div> <div class="A0"></div> <div class="A3"></div> <div class="A0"></div> <div class="A3"></div> <script src="BBB.js"></script> </body> </html>
出现问题的代码示例(仅第二次鼠标悬停时span才变色)
JavaScript
const important = [...document.getElementsByClassName('important')]; console.log(`const 'important' equals:`); console.log(important); console.log('---- ---- ---- ----'); const Classes = [ "color-c0", "color-c1", "color-c2", "color-c3", "color-c4", "color-c5", "color-c6", "color-c7", ]; const Entries = [ "c0-enter", "c1-enter", "c2-enter", "c3-enter", "c4-enter", "c5-enter", "c6-enter", "c7-enter", ]; const Leaves = [ "c0-leave", "c1-leave", "c2-leave", "c3-leave", "c4-leave", "c5-leave", "c6-leave", "c7-leave", ]; for (let i = 0; i < important.length; i++) { const allBlocks = [...document.querySelectorAll(`span[id *= block-A${i}] span[class *= color-c]`)]; for (let each of allBlocks) { each.onmouseover = (e) => { let currentBlock = e.target.parentNode; let currentGroup = [...currentBlock.querySelectorAll(`span[class *= color-c]`)]; console.log(`const 'currentGroup' of 'currentBlock [${i}]' equals:`); console.log(currentGroup); console.log('---- ---- ---- ----'); for (let a = 0; a < Classes.length; a++) { let filteredGroups = currentGroup.filter(span => (span.className.match(`color-c${a}`))); console.log(`let 'filteredGroup [${a}]' of 'currentBlock [${i}]' equals:`); console.log(filteredGroups); console.log('---- ---- ---- ----'); for (let all of filteredGroups) { console.log(`this works the FIRST time!!`); all.onmouseover = () => { for (let b = 0; b < filteredGroups.length; b++) { console.log(`this works the SECOND time!!`); filteredGroups[b].classList.remove(`${Leaves[a]}`); filteredGroups[b].classList.add(`${Entries[a]}`); }; }; all.onmouseout = () => { for (let b = 0; b <filteredGroups.length; b++) { filteredGroups[b].classList.remove(`${Entries[a]}`); filteredGroups[b].classList.add(`${Leaves[a]}`); }; }; }; }; }; }; };
CSS
.c0-enter { animation: c0-enter 332ms ease 0s 1 normal forwards; } @keyframes c0-enter { 0% { background-color: red; color: black; } 100% { background-color: red; color: white; } } .c0-leave { animation: c0-leave 0.3s ease 0s 1 normal forwards; } @keyframes c0-leave { 0% { color: white; background-color: red; } 100% { color: unset; background-color: unset; } } /* DIVIDER */ .c1-enter { animation: c1-enter 332ms ease 0s 1 normal forwards; } @keyframes c1-enter { 0% { background-color: blue; color: black; } 100% { background-color: blue; color: white; } } .c1-leave { animation: c1-leave 0.3s ease 0s 1 normal forwards; } @keyframes c1-leave { 0% { color: white; background-color: blue; } 100% { color: unset; background-color: unset; } } /* DIVIDER */ .c2-enter { animation: c2-enter 332ms ease 0s 1 normal forwards; } @keyframes c2-enter { 0% { background-color: black; color: black; } 100% { background-color: black; color: white; } } .c2-leave { animation: c2-leave 0.3s ease 0s 1 normal forwards; } @keyframes c2-leave { 0% { color: white; background-color: black; } 100% { color: unset; background-color: unset; } } /* DIVIDER */ .c3-enter { animation: c3-enter 332ms ease 0s 1 normal forwards; } @keyframes c3-enter { 0% { background-color: rgb(94, 94, 120); color: black; } 100% { background-color: rgb(94, 94, 120); color: white; } } .c3-leave { animation: c3-leave 0.3s ease 0s 1 normal forwards; } @keyframes c3-leave { 0% { color: white; background-color: rgb(94, 94, 120); } 100% { color: unset; background-color: unset; } }
HTML
<!DOCTYPE html> <html lang="en"> <head> <title>Document</title> <link rel="stylesheet" href="a.css"> </head> <body> <span id="block-A0" class="important"> <p style="text-align: center; font-size: 40px; margin-top: 20%; cursor: default;"> <span class="color-c0">Color 1</span> <span class="color-c1">Color 2</span> <span class="color-c2">Color 3</span> <span class="color-c3">Color 4</span> </p> </span> <script src="a.js"></script> </body> </html>
原因分析
核心问题是事件绑定时机错误,和for-of循环本身无关:
- 你把
filteredGroups元素的onmouseover/onmouseout事件绑定逻辑,嵌套在了外层each.onmouseover的回调函数里。 - 第一次鼠标悬停时,仅执行了外层的mouseover回调,完成了内层事件的绑定,但并未触发实际的动画切换逻辑。
- 第二次鼠标悬停时,已绑定好的内层mouseover事件才会执行,因此出现首次无响应的现象。
解决方法
直接在初始化阶段完成所有事件绑定,不需要嵌套在另一个mouseover回调中。修改后的JavaScript代码如下:
const important = [...document.getElementsByClassName('important')]; console.log(`const 'important' equals:`); console.log(important); console.log('---- ---- ---- ----'); const Entries = [ "c0-enter", "c1-enter", "c2-enter", "c3-enter", "c4-enter", "c5-enter", "c6-enter", "c7-enter", ]; const Leaves = [ "c0-leave", "c1-leave", "c2-leave", "c3-leave", "c4-leave", "c5-leave", "c6-leave", "c7-leave", ]; for (let i = 0; i < important.length; i++) { const allBlocks = [...document.querySelectorAll(`span[id *= block-A${i}] span[class *= color-c]`)]; // 直接遍历目标元素绑定事件,无需外层mouseover触发 for (let each of allBlocks) { // 匹配当前元素对应的color-c索引 const match = each.className.match(/color-c(\d)/); if (!match) continue; const colorIndex = parseInt(match[1]); each.onmouseover = () => { // 获取当前block下的同组元素 const currentBlock = each.closest(`span[id *= block-A${i}]`); const sameColorSpans = [...currentBlock.querySelectorAll(`span.color-c${colorIndex}`)]; sameColorSpans.forEach(item => { item.classList.remove(Leaves[colorIndex]); item.classList.add(Entries[colorIndex]); }); }; each.onmouseout = () => { const currentBlock = each.closest(`span[id *= block-A${i}]`); const sameColorSpans = [...currentBlock.querySelectorAll(`span.color-c${colorIndex}`)]; sameColorSpans.forEach(item => { item.classList.remove(Entries[colorIndex]); item.classList.add(Leaves[colorIndex]); }); }; }; };
优化点说明
- 移除嵌套的mouseover绑定逻辑,直接在初始化阶段给目标span绑定事件。
- 通过正则匹配直接获取当前span对应的
color-c索引,避免循环遍历所有类名。 - 使用
closest方法精准定位所属的block容器,替代原有的parentNode方式。 - 用
forEach替代for循环,简化代码逻辑。
内容的提问来源于stack exchange,提问作者user26545514
相关产品推荐
相关产品推荐

