事件捕获是事件流的第一阶段,从window向目标元素逐层下行,需显式启用capture: true;它与冒泡方向相反、时机在前,适用于全局预处理,而事件委托依赖冒泡因其天然支持子元素事件向父元素传递。
事件捕获不是备选方案,而是事件传播中真实存在的、早于目标阶段的阶段。当你点击一个嵌套的 button,浏览器会先从 window 开始,依次经过 document → html → body → 父容器 → 目标元素,这个过程就是捕获阶段。
它默认不生效——你必须显式启用:
element.addEventListener('click', handler, true);
// 或更现代写法:
element.addEventListener('click', handler, { capture: true });addEventListener 的现代浏览器有效(IE9+,所有主流现代浏览器均无兼容问题)onclick = fn)**完全不支持捕获**,只走冒泡它们不是“两种可互换的写法”,而是事件流中不可跳过的两个阶段,顺序固定:捕获 → 目标 → 冒泡。
capture: true 是由外向内(window → 目标),capture: false(默认)是由内向外(目标 → window)e.stopPropagation() 会同时中断当前阶段后续路径(含捕获或冒泡),但不影响同阶段已注册的其他监听器;而 e.stopImmediatePropagation() 会立刻终止该阶段所有剩余监听器事件委托的本质是“父元素代子元素收事件”,这只有在子元素事件能自然到达父元素时才成立——而冒泡正提供了这种向上传递的能力。
捕获做不到这点:它从外往里走,父元素的捕获监听器会在子元素之前触发,此时你拿到的 e.target 确实是子元素,但逻辑上它还没“落到”子元素身上,容易造成语义错乱(比如想对被点击的 li 做操作,却在父 ul 的捕获阶段就执行,而此时子元素可能还未完成渲染或初始化)。
list.addEventListener('click', e => { if (e.target.matches('li')) { /* 处理 */ } });
e.target 虽正确,但时机过早,且违背委托“等事件真正发生后再响应”的直觉pointer-events: none 会彻底中断冒泡)很多人调试时卡在“为什么我绑了捕获没反应”,其实问题常出在细节上:
capture: true,但子元素用了——捕获阶段根本不会走到子元素(因为捕获是自上而下,子元素监听器只在它自己被“路过”时才触发)onclick 和 addEventListener 时,前者永远只在冒泡阶段运行,无法与捕获监听器形成对称控制e.preventDefault() 是有效的(比如阻止链接跳转),但它发生在目标阶段之前,需确认是否真要在此刻干预捕获不是“高级冒泡”,它是事件流中一
条独立、前置、有明确用途的通路;理解它不为炫技,而是在需要提前介入(如埋点、权限、防误触)时,手上有把准头够的刀。