記錄js中的冒泡事件
https://chat.deepseek.com/share/0uyt5lgfv5jrac6frn
该对话来自分享,由 AI 生成,请仔细甄别。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="../bootstrap-5.3.8/dist/css/bootstrap.css" rel="stylesheet" /> <link href="../icons-1.13.1/font/bootstrap-icons.css" rel="stylesheet" /> <link href="./my_menu_test.css" rel="stylesheet" /> </head> <body> <div class="body-content"> <!-- 菜單 --> <div class="menu-wrapper"> <div class="menu-content"> <ul> <li class="menu-item active" data-menu-type="ctlmenu"> <a href="#"> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單001</p> <i class="bi bi-chevron-left right-icon"></i> </a> <ul> <li class="menu-item" data-menu-type="action"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單00111</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item" data-menu-type="action"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單00112</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item" data-menu-type="action"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單00113</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> </ul> </li> <li class="menu-item" data-menu-type="ctlmenu"> <a href="#"> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單002</p> <i class="bi bi-chevron-left right-icon"></i> </a> <ul> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單003</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單004</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單005</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> </ul> </li> <li class="menu-item" data-menu-type="ctlmenu"> <a href="#"> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單006</p> <i class="bi bi-chevron-left right-icon"></i> </a> <ul> <li class="menu-item" data-menu-type="action"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單007</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item" data-menu-type="action"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單008</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item" data-menu-type="ctlmenu"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單009</p> <i class="bi bi-chevron-left right-icon"></i> </a> <ul> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單010</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單011</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item"> <a href=""> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單012</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> </ul> </li> </ul> </li> <li class="menu-item"> <a href="#"> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單013</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> <li class="menu-item"> <a href="#"> <i class="bi bi-file-earmark-spreadsheet"></i> <p>菜單014</p> <i class="bi bi-chevron-left right-icon"></i> </a> </li> </ul> </div> </div> <!-- 右側內容區 --> <div class="main-content"> <!-- 內容區頭部 --> <div class="content-header"> <a href="#" class="user-info">用戶信息</a> </div> <!-- 內容區中間部分 --> <div class="content-body"> <div class="tab-headers"> <ul class="tab-list"> <li class="tab-item"> <a href="javascript:void(0)">tab1</a> <span class="tab-close">×</span> </li> <li class="tab-item"> <a href="javascript:void(0)">tab1111111111111111111111</a> <span class="tab-close">×</span> </li> <li class="tab-item"> <a href="javascript:void(0)">tab1</a> <span class="tab-close">×</span> </li> </ul> </div> <div class="tab-content"> tab-內容區中間部分 </div> </div> <!-- 內容區 底部 --> <div class="content-footer"> Copyright@foxconn.com 2025 </div> </div> </div> <script type="script" src="../bootstrap-5.3.8/dist/js/bootstrap.bundle.js"></script> <script> document.addEventListener('DOMContentLoaded',AddMenuItemClickEvent); // 所有的菜單li添加點擊事件 function AddMenuItemClickEvent(){ const ctlMenus=document.querySelectorAll('.menu-item'); ctlMenus.forEach(menuItem=>{ menuItem.addEventListener('click',(event)=>{ event.preventDefault(); // 检查点击的是否是当前菜单项的直接子链接 const clickMenu = event.target.closest('[data-menu-type]'); const menuType = clickMenu.dataset.menuType; alert(menuType); // if(clickedLink && clickedLink === menuItem) { // event.preventDefault(); // menuItem.classList.toggle("active"); // event.stopPropagation(); // 阻止事件继续冒泡 // } }) }) } </script> </body> </html> 請分析這段代碼,只分析我代碼出現的原因,不要重新去寫代碼 為什麼我點擊 “菜單00111” 會彈出2次action
分析你的代码,点击"菜單00111"弹出2次 action 的原因如下:
问题根源:事件冒泡 + 错误的选择器
1. 事件绑定问题
javascript
const ctlMenus=document.querySelectorAll('.menu-item');
这行代码选择了所有的 .menu-item 元素,包括:
-
父级菜单项(如"菜單001")
-
子级菜单项(如"菜單00111")
2. DOM 结构分析
当你点击"菜單00111"时,HTML结构是:
html
<li class="menu-item active" data-menu-type="ctlmenu"> <!-- 父级 --><a href="#">菜單001</a><ul><li class="menu-item" data-menu-type="action"> <!-- 子级 --><a href="">菜單00111</a></li></ul> </li>
3. 事件触发流程
-
第一次触发:点击"菜單00111"时,事件冒泡到其父元素
<li class="menu-item" data-menu-type="action"> -
第二次触发:事件继续冒泡到祖父元素
<li class="menu-item active" data-menu-type="ctlmenu">
4. 关键问题代码
javascript
const clickMenu = event.target.closest('[data-menu-type]');
-
第一次:找到
data-menu-type="action"(正确的) -
第二次:找到
data-menu-type="ctlmenu"(父级的)
总结
因为事件绑定到了所有层级的 .menu-item,且事件冒泡导致点击一个子菜单项时,会同时触发其所有父级菜单项的点击事件,所以会弹出2次警告框。
找到父級的時候,為什麼彈出的不是ctlmenu,而是兩次都是相同的信息“action”
你提出了一个很好的观察点。让我重新分析这个问题:
关键问题在于 event.target.closest() 的使用
代码逻辑分析:
javascript
const clickMenu = event.target.closest('[data-menu-type]');
const menuType = clickMenu.dataset.menuType;
alert(menuType);
实际点击时的DOM路径:
text
<li data-menu-type="ctlmenu"> (祖父级)↳ <li data-menu-type="action"> (父级) ← 事件绑定在这里↳ <a>菜單00111</a> ← 实际点击位置
两次触发时的 event.target 不同:
第一次触发(直接点击子菜单项时):
-
event.target=<a>菜單00111</a>(实际点击的链接) -
event.target.closest('[data-menu-type]')向上查找 -
找到:
<li data-menu-type="action">(父级) -
弹出:
"action"
第二次触发(事件冒泡到祖父级时):
-
event.target仍然 =<a>菜單00111</a>(事件源不变) -
event.target.closest('[data-menu-type]')再次向上查找 -
找到:仍然是
<li data-menu-type="action">(因为从同一个事件源向上查找) -
弹出:
"action"
为什么不是 "ctlmenu"?
因为 event.target 在整个事件冒泡过程中保持不变,始终指向最初被点击的元素(<a>菜單00111</a>)。closest() 方法从当前元素向上查找,所以两次都找到同一个最近的 data-menu-type 元素。
正确的应该是:
javascript
// 应该使用 event.currentTarget 而不是 event.target.closest() const menuType = event.currentTarget.dataset.menuType;
总结:你看到两次 "action" 是因为 event.target 在冒泡过程中保持不变,而 closest() 每次都从同一个起点向上查找,找到的是同一个最近的 data-menu-type 元素。
