事件冒泡基本原理

一、核心概念

事件冒泡(Event Bubbling)是 JavaScript DOM 事件流的核心机制之一:当一个元素触发某事件(如点击、输入)后,该事件会从目标元素开始,逐级向上传播到它的父元素、祖先元素,直至 window 对象。

简单理解:就像水中的气泡,从水底(目标元素)向上冒到水面(window)。

二、DOM 事件流的三个阶段

完整的 DOM 事件流包含 3 个阶段,事件冒泡是最后一环:

阶段 说明
捕获阶段 事件从 window 开始,向下逐层传递到目标元素(路过的祖先元素会触发捕获阶段的监听器)。
目标阶段 事件到达真正触发的目标元素,执行该元素的事件监听器。
冒泡阶段 事件从目标元素开始,向上逐层传递回 window(路过的祖先元素会触发冒泡阶段的监听器)。

注意:通过 addEventListener 绑定事件时,第三个参数 useCapture 可控制监听阶段:

  • false(默认):在冒泡阶段触发监听器;
  • true:在捕获阶段触发监听器。

三、事件冒泡的直观示例

假设有如下 HTML 结构(三层嵌套):

1
2
3
4
5
6
7
8
9
<div id="grandpa" style="padding: 40px; background: #eee;">
爷爷元素
<div id="father" style="padding: 40px; background: #ddd;">
爸爸元素
<div id="son" style="padding: 40px; background: #ccc;">
儿子元素(点击我)
</div>
</div>
</div>

给三个元素都绑定冒泡阶段的点击事件:

1
2
3
4
5
6
7
8
9
10
11
document.getElementById('grandpa').addEventListener('click', () => {
console.log('爷爷元素被触发(冒泡阶段)');
});

document.getElementById('father').addEventListener('click', () => {
console.log('爸爸元素被触发(冒泡阶段)');
});

document.getElementById('son').addEventListener('click', () => {
console.log('儿子元素被触发(目标阶段)');
});

点击“儿子元素”时,控制台输出顺序

1
2
3
儿子元素被触发(目标阶段)
爸爸元素被触发(冒泡阶段)
爷爷元素被触发(冒泡阶段)

这就是事件冒泡的典型表现:事件从目标元素“向上冒”,依次触发父级、祖父级的同类型事件。

四、事件对象的关键属性

通过事件对象 event,可精准控制冒泡过程:

属性 说明
event.target 真正触发事件的目标元素(始终不变,如上例中的 son)。
event.currentTarget 当前正在处理事件的元素(随冒泡变化,如 fathergrandpa)。
event.eventPhase 当前事件流阶段:1(捕获)、2(目标)、3(冒泡)。

五、阻止事件冒泡

若不想让事件继续向上冒泡,可通过以下方法阻止:

1. event.stopPropagation()

最常用的方法,阻止事件继续向上冒泡,但不影响当前元素的其他监听器

1
2
3
4
document.getElementById('son').addEventListener('click', (e) => {
e.stopPropagation(); // 阻止冒泡
console.log('儿子元素被触发,冒泡已停止');
});

此时点击“儿子元素”,只会触发自身的事件,爸爸和爷爷的事件不会执行。

2. event.stopImmediatePropagation()

更彻底的方法:不仅阻止冒泡,还阻止当前元素后续绑定的同类型监听器

1
2
3
4
5
6
7
8
document.getElementById('son').addEventListener('click', (e) => {
e.stopImmediatePropagation();
console.log('第一个监听器');
});

document.getElementById('son').addEventListener('click', () => {
console.log('第二个监听器(不会执行)');
});

注意:阻止冒泡 ≠ 阻止默认行为(如链接跳转、表单提交)。阻止默认行为需用 event.preventDefault()

六、事件冒泡的应用:事件委托(代理)

事件冒泡最实用的场景是事件委托:利用冒泡机制,给父元素绑定一个监听器,统一处理所有子元素的同类型事件。

示例:动态列表的点击处理

假设有一个动态添加项的列表:

1
2
3
4
5
<ul id="list">
<li>第1项</li>
<li>第2项</li>
<!-- 后续会动态添加更多 li -->
</ul>

不用给每个 li 单独绑定事件,直接给父级 ul 绑定:

1
2
3
4
5
6
document.getElementById('list').addEventListener('click', (e) => {
// 通过 e.target 判断点击的是 li
if (e.target.tagName === 'LI') {
console.log('点击了:', e.target.textContent);
}
});

事件委托的优势

  1. 减少内存占用:无需给每个子元素绑定监听器;
  2. 适配动态元素:后续通过 JS 新增的子元素,也能自动触发事件。

七、注意事项

  1. 不是所有事件都冒泡

    • 不冒泡的事件:focusblurloadunloadmouseentermouseleave(可用 mouseover/mouseout 替代,它们会冒泡);
    • 会冒泡的事件:clickinputkeydownkeyupmouseovermouseout 等。
  2. 冒泡顺序与 DOM 结构一致
    即使子元素通过 CSS 定位(如 position: absolute)脱离文档流,冒泡仍严格按照 HTML 中的嵌套顺序向上传播。

  3. 性能考虑
    避免给最顶层的 window/document 绑定过多复杂的冒泡事件,可能导致页面卡顿。


事件冒泡基本原理
https://cszy.top/2015-01-19 jqueryjs阻止事件冒泡/
作者
csorz
发布于
2015年1月19日
许可协议