記錄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">&times;</span> </li> <li class="tab-item"> <a href="javascript:void(0)">tab1111111111111111111111</a> <span class="tab-close">&times;</span> </li> <li class="tab-item"> <a href="javascript:void(0)">tab1</a> <span class="tab-close">&times;</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. 事件触发流程

  1. 第一次触发:点击"菜單00111"时,事件冒泡到其父元素 <li class="menu-item" data-menu-type="action">

  2. 第二次触发:事件继续冒泡到祖父元素 <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 元素。

### 原理 #### 事件冒泡 事件冒泡是指当一个元素上的事件被触发时,该事件会从这个元素开始,逐层向上传播到它的父元素、祖父元素,直到传播到文档对象(`document`)。例如,当在一个按钮上点击时,点击事件首先会在按钮元素上触发,然后依次在按钮的父元素、父元素的父元素等元素上触发,就像气泡从水底逐渐上升到水面一样。 在引用中的代码示例里,当点击 `button` 元素时,由于使用了事件冒泡(`addEventListener` 的第三个参数为 `false`,默认就是冒泡阶段触发),事件会先在 `button` 元素上触发,执行对应的回调函数,然后依次在 `third`、`second`、`first` 元素上触发相应的 `click` 事件回调函数,将元素的 `id` 依次添加到 `.eventOrder` 元素中,展示出事件冒泡的顺序。 #### 事件捕捉 事件捕捉与事件冒泡相反,当一个元素上的事件被触发时,事件会从文档对象(`document`)开始,逐层向下传播到目标元素。也就是先从最顶层的 `document` 开始,依次经过各级父元素,最后到达实际触发事件的元素。要使用事件捕捉,需要将 `addEventListener` 方法的第三个参数设置为 `true`。 ### 应用 #### 事件冒泡的应用 - **事件委托**:可以将事件处理程序绑定到一个父元素上,而不是每个子元素都绑定。这样当子元素上的事件触发时,会冒泡到父元素上执行相应的处理程序。这种方式可以减少事件处理程序的数量,提高性能。例如,在一个列表中,每个列表项都有一个点击事件,如果为每个列表项都绑定点击事件,当列表项很多时会消耗大量内存。可以将点击事件处理程序绑定到列表的父元素上,利用事件冒泡来处理列表项的点击事件。 ```html <!DOCTYPE html> <html lang="en"> <body> <ul id="list"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> <script> const list = document.getElementById('list'); list.addEventListener('click', function (event) { if (event.target.tagName === 'LI') { console.log('Clicked on: ' + event.target.textContent); } }); </script> </body> </html> ``` - **表单验证**:可以在表单的父元素上绑定一个提交事件处理程序,当表单中的某个输入框触发提交事件时,事件冒泡到父元素上进行统一的表单验证。 #### 事件捕捉的应用 - **全局事件监控**:可以在 `document` 上使用事件捕捉来监控整个页面的某些事件,比如鼠标移动事件。当鼠标移动时,事件会从 `document` 开始捕捉,这样可以在事件到达具体元素之前进行一些全局的处理,例如记录鼠标移动的轨迹等。 ```javascript document.addEventListener('mousemove', function (event) { console.log('Mouse moved at: ' + event.clientX + ', ' + event.clientY); }, true); ``` - **安全控制**:在一些需要严格安全控制的场景中,可以在事件到达目标元素之前,通过事件捕捉进行权限检查等操作,如果不符合条件可以阻止事件继续传播。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值