事件流分捕获、目标、冒泡三阶段,addEventListener的useCapture参数决定监听器触发阶段;stopPropagation()会终止整个事件流,无法单独阻止冒泡或捕获。

事件冒泡和捕获是 JavaScript 事件流的两个阶段,决定事件如何在 DOM 树中传播。它们不是可选“模式”,而是浏览器固有行为——你无法禁用其中一者,但可以控制监听器在哪个阶段触发。
addEventListener 的第三个参数决定监听阶段
关键在于 addEventListener 的第三个参数:useCapture。它是个布尔值,默认为 false(冒泡阶段),设为 true 则在捕获阶段触发。
- 冒泡阶段:事件从目标元素向上逐级传到
document(如:button → div → body → html → document) - 捕获阶段:事件从
document向下逐级传到目标元素(如:document → html → body → div → button) - 目标阶段本身既不属于纯捕获也不属于纯冒泡,但会触发所有绑定的监听器(无论
useCapture是 true 还是 false)
事件触发顺序:捕获 → 目标 → 冒泡
一个点击事件完整流程是:先走捕获路径(从外向内),到达目标后执行目标上的监听器,再走冒泡路径(从内向外)。如果多个监听器绑在同一元素上,useCapture=true 的总在 useCapture=false 之前执行。
document.addEventListener('click', () => console.log('document capture'), true);
document.body.addEventListener('click', () => console.log('body capture'), true);
document.body.addEventListener('click', () => console.log('body bubble'), false);
document.getElementById('btn').addEventListener('click', () => console.log('button target'), false);
document.getElementById('btn').addEventListener('click', () => console.log('button target again'), true);
点击按钮时输出顺序为:document capture → body capture → button target again → button target → body bubble
立即学习“Java免费学习笔记(深入)”;
stopPropagation() 会同时阻断捕获和冒泡
event.stopPropagation() 并不区分阶段——它一旦被调用,整个事件流立即终止,后续所有阶段(包括同级其他监听器)都不会执行。
- 在捕获阶段调用,后续捕获监听器、目标阶段、冒泡阶段全跳过
- 在目标阶段调用,冒泡阶段直接中断;但注意:同一元素上
useCapture=true和useCapture=false的监听器都已执行完毕,因为目标阶段是共享的 - 想只阻止冒泡而保留捕获?做不到。浏览器不提供“仅停冒泡”或“仅停捕获”的 API
日常开发中捕获阶段极少显式使用
绝大多数业务逻辑依赖冒泡(比如委托给 ul 处理所有 li 点击),所以 useCapture 很少写成 true。唯一常见例外是全局防误触或早期拦截:
- 在
document捕获阶段监听click,快速判断是否点在某个弹窗外部,然后关闭它 - 某些 UI 库在根容器用捕获监听键盘事件,确保
Esc总能被最早捕获并处理 - 注意:React 的合成事件默认只支持冒泡,
onMouseDownCapture这类带Capture后缀的 props 才进入捕获阶段
真正容易出问题的是嵌套监听器 + stopPropagation() + 混用 useCapture,这时候事件流走向很难靠直觉推断,必须动手打印验证。











