当 capture 和 bubble 同时存在时,浏览器会分两个阶段执行:先自上而下触发所有捕获阶段监听器,再自下而上触发所有冒泡阶段监听器。具体做法是在 addeventlistener 中分别设置第三个参数为 true(捕获)和 false(冒泡),事件对象会按 dom 层级顺序依次调用这些回调。

如何区分捕获与冒泡的执行时机
捕获阶段发生在事件从根节点向目标节点传播的过程中,而冒泡阶段发生在事件从目标节点向根节点回溯的过程中。要观察这种差异,可以在父元素上同时注册两种模式的监听器,通过控制台输出顺序即可验证。捕获阶段的回调会在目标元素自身的任何监听器之前执行,而冒泡阶段的回调则在之后执行。

const parent = document.getElementById('parent');
const child = document.getElementById('child');
// 捕获阶段:true
parent.addEventListener('click', () => {
console.log('Parent Capture');
}, true);
// 冒泡阶段:false
parent.addEventListener('click', () => {
console.log('Parent Bubble');
}, false);
child.addEventListener('click', () => {
console.log('Child Target');
});
// 点击 child 时的输出顺序:
// 1. Parent Capture
// 2. Child Target
// 3. Parent BubblestopPropagation 对两个阶段的影响
在捕获或冒泡阶段调用 event.stopPropagation() 会阻止事件继续向后续节点传播,但不会取消当前已触发的监听器。如果希望在捕获阶段就终止整个流程,需要在捕获监听器中调用该方法;同样,如果在冒泡阶段调用,则后续的冒泡监听器和更高层级的捕获监听器(如果有的话)将不会被触发。需要注意的是,一旦进入冒泡阶段,之前的捕获阶段已经执行完毕,无法回退。
立即学习“Java免费学习笔记(深入)”;

const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', (e) => {
console.log('Parent Capture Start');
e.stopPropagation(); // 阻止继续向下传播
console.log('Parent Capture End');
}, true);
child.addEventListener('click', (e) => {
console.log('Child Target'); // 这行代码不会执行
}, false);
parent.addEventListener('click', (e) => {
console.log('Parent Bubble'); // 这行代码也不会执行
}, false);
// 点击 child 时的输出顺序:
// 1. Parent Capture Start
// 2. Parent Capture End
// 注意:Child Target 和 Parent Bubble 都不会被触发基于同一输入条件下捕获与冒泡的优先级
在同一 DOM 节点上,如果同时注册了捕获和冒泡监听器,捕获阶段的监听器总是优先于冒泡阶段的监听器执行。这是因为事件流的设计机制决定了必须先完成从根到目标的捕获过程,才能开始从目标到根的冒泡过程。因此,即使冒泡监听器先被注册,它也会在捕获监听器之后才执行。这种优先级是固定的,不受注册顺序影响。


















