如何理解javascript中的事件冒泡与捕获【教程】

事件冒泡和捕获是浏览器默认执行的两个阶段:捕获从window到目标元素下行,需显式启用监听;冒泡从目标元素向上传播,为默认行为,支持事件委托。

事件冒泡和捕获不是“要不要用”的选择,而是浏览器默认就发生的两个阶段——你没写任何代码,它们也在运行。

事件捕获阶段:从 window 到目标元素的“下行路径”

当点击一个嵌套很深的 时,事件会先从 window 开始,依次经过 document、外层 ……最后到达那个 。这个过程叫捕获阶段。

只有显式开启才能监听它:addEventListener(type, handler, true)addEventListener(type, handler, { capture: true })

  • 默认不启用,所以大多数 addEventListener 都只响应冒泡阶段
  • 捕获阶段无法通过 event.stopPropagation() 中断冒泡(因为冒泡还没开始)
  • 适合做全局拦截,比如在 捕获所有点击前统一做权限判断

事件冒泡阶段:从目标元素到 window 的“上行路径”

点击 后,事件会反过来,从它自

己出发,逐级向上传给父 documentwindow。这是默认行为,也是日常最常打交道的阶段。

几乎所有 DOM 事件都支持冒泡(focusblurmouseenter 等少数几个不冒泡)。

  • event.stopPropagation() 可阻止后续冒泡,但不影响当前节点上的其他监听器执行
  • event.stopImmediatePropagation() 还会跳过同一节点上尚未执行的其他监听器
  • 委托事件(如 ul.addEventListener('click', e => {...}) 处理内部
  • )完全依赖冒泡

为什么 onclick 属性和 addEventListener 行为看起来一样?

因为 onclick="..." 和不带第三参数的 addEventListener(..., ..., false) 都绑定在冒泡阶段,且无法切换到捕获阶段。

这导致一个常见误解:以为“没写 true 就没捕获”。其实捕获一直存在,只是你没监听它。

  • onclick 属性本质是 addEventListener('click', handler, false) 的语法糖
  • 同一个元素上多个 addEventListener,按注册顺序执行;但 onclick 会被后设的覆盖
  • 现代代码应优先用 addEventListener,避免隐式覆盖和阶段不可控

调试时怎么确认当前在哪个阶段?

event.eventPhase:1 = 捕获中(CAPTURING_PHASE),2 = 目标本身(AT_TARGET),3 = 冒泡中(BUBBLING_PHASE)。

别靠猜,加一行 console.log(event.eventPhase) 最直接。

  • 目标元素上同时绑了捕获和冒泡监听器?eventPhase === 2 时两者都会触发(捕获先、冒泡后)
  • event.currentTarget 区分“谁在处理”,用 event.target 看“点的是谁”——冒泡中二者经常不同
  • 移动端某些事件(如 touchstart)可能因浏览器优化略过部分阶段,不能假设一定完整走完

真正容易被忽略的,是捕获阶段的存在本身——它不报错、不警告、也不影响日常开发,直到你需要在某个祖先元素上“提前干预”事件时,才发现原来得把第三个参数设成 true 才能接住。