JavaScript 中的 事件冒泡(Event Bubbling) 和 事件捕获(Event Capturing) 是 DOM 事件传播机制的两个阶段。理解它们对事件监听、委托和防止冲突至关重要。
什么是事件传播?
三个阶段:
- 捕获阶段(Capturing Phase)
- 目标阶段(Target Phase)
- 冒泡阶段(Bubbling Phase)
🔄 三个阶段图示:
假设 HTML 结构如下:
bash
<div id="outer">
<button id="inner">Click me</button>
</div>
点击按钮时,事件传播顺序:
markdown
1. document
2. html
3. body
4. #outer
5. #inner ← 【目标阶段】
6. #outer
7. body
8. html
9. document
如何监听不同阶段的事件?
使用 addEventListener
时,第三个参数可以控制监听的是捕获阶段还是冒泡阶段:
arduino
// 捕获阶段
outer.addEventListener("click", handler, true);
// 冒泡阶段(默认)
outer.addEventListener("click", handler); // 等价于 false
🛑 如何阻止事件传播?
✅ 阻止冒泡
csharp
event.stopPropagation();
✅ 阻止默认行为(如点击链接跳转)
csharp
event.preventDefault();
⚠️ 面试常见考点总结
概念 | 捕获(Capturing) | 冒泡(Bubbling) |
---|---|---|
顺序 | 从外向内(父 → 子) | 从内向外(子 → 父) |
是否默认启用 | ❌(需手动传 true) | ✅ 默认启用 |
用途 | 少见、用于特定控制场景 | 常用于事件委托 |
📝面试回答模板:
JS 的事件传播分为捕获、目标和冒泡三个阶段。事件默认是从外层向目标元素传播(捕获阶段),然后从目标向外传播(冒泡阶段)。我们通常监听冒泡阶段来做事件处理,也可以通过
addEventListener
的第三个参数控制监听阶段,并用stopPropagation()
来阻止继续传播。
🤔你是不是不明白捕获阶段是用来干什么的,冒泡阶段是用来干什么的?
✅ 先讲结论:
阶段 | 用途(干什么) | 实际开发中是否常用 |
---|---|---|
捕获阶段 | 拦截事件到达目标元素之前的过程 | ❌ 很少使用 |
冒泡阶段 | 处理已经发生的事件或用来"事件委托" | ✅ 非常常用 |
用现实生活场景解释给你听:
想象你在商场(
document
)里,有一位顾客(用户)点了商场里某家店(button
)的产品。
- 捕获阶段: 商场保安从外往里拦截顾客(提前干预事件)
- 目标阶段: 顾客抵达商店(事件目标元素)
- 冒泡阶段: 顾客走出商场回家(事件往上传播)
🎯 捕获阶段:干嘛用?
虽然不常用,但可以用于一些高级拦截的场景:
✅ 用例:全局提前"拦截"用户行为
javascript
document.addEventListener("click", e => {
console.log("你点了什么东西,我先知道!");
}, true); // 捕获阶段
✅ 用例:阻止事件到达目标元素
javascript
parent.addEventListener("click", (e) => {
e.stopPropagation();
console.log("我拦住了,子元素根本不会响应");
}, true); // 捕获阶段
🫧 冒泡阶段:干嘛用?
这是我们在前端开发中最常使用的阶段。
✅ 最主要用途:事件委托(Event Delegation)
例如:一个列表中动态插入很多按钮,你不需要给每个按钮都绑定点击事件。
javascript
document.querySelector("ul").addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
console.log("你点击了某个列表项", e.target.textContent);
}
});
- 效率高(只绑定一次)
- 动态添加的元素也能响应
📌 总结:
- 捕获: 提前处理、阻止传播(高级用法)
- 冒泡: 用于绝大多数事件监听,尤其是事件委托