javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > js事件流从捕获到冒泡

JavaScript 事件流:从"捕获"到"冒泡"的完整旅程

作者:yqcoder

这篇文章给大家介绍JavaScript事件流:从“捕获”到“冒泡”的完整旅程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

当你点击页面上的一个按钮时,这个点击动作并不是瞬间只在按钮上发生的。
在浏览器的世界里,这个点击事件像一阵风,经历了一场从上到下,再从下到上的完整旅行。

这就是 DOM 事件流(Event Flow)

1. 🤔 什么是事件流?

定义
事件流描述的是从页面中接收事件的顺序。

在早期的浏览器大战中, Netscape(网景)和 IE 提出了两种完全相反的事件传播模型:

最终,W3C 制定了标准,将两者结合:先捕获,后冒泡

通俗比喻
想象你在一个多层办公楼里扔了一个石子(触发事件)。

  1. 捕获阶段:石子从楼顶(window)一层层往下掉,经过每一层的地板。
  2. 目标阶段:石子落在了某一层的具体房间(目标元素)里。
  3. 冒泡阶段:石子落地后产生的回声或震动,从那个房间一层层往上传播回楼顶。

2. 🎬 事件流的三个阶段

当一个事件发生时,它会依次经历以下三个阶段:

第一阶段:捕获阶段(Capturing Phase)

第二阶段:目标阶段(Target Phase)

第三阶段:冒泡阶段(Bubbling Phase)

      Window
        |
      Document
        |
       HTML
        |
       Body          <--- 1. 捕获阶段 (向下)
        |            <--- 2. 目标阶段 (到达 div#target)
      Div#container  <--- 3. 冒泡阶段 (向上)
        |
      Div#target     (你点击了这里)

3. 💻 代码实战:如何控制捕获与冒泡?

在 JavaScript 中,我们使用 addEventListener 来注册事件。它的第三个参数决定了监听器在哪个阶段触发。

语法

element.addEventListener(event, handler, useCapture);

🧪 实验代码

假设我们有如下 HTML 结构:

<div id="grandparent">
  爷爷
  <div id="parent">
    爸爸
    <div id="child">儿子</div>
  </div>
</div>

我们给每一层都绑定点击事件,分别测试捕获和冒泡:

const grandparent = document.getElementById("grandparent");
const parent = document.getElementById("parent");
const child = document.getElementById("child");
function log(msg, phase) {
  console.log(`${msg} - ${phase}`);
}
// 1. 注册捕获阶段监听器 (useCapture = true)
grandparent.addEventListener(
  "click",
  () => log("Grandparent", "Capture"),
  true,
);
parent.addEventListener("click", () => log("Parent", "Capture"), true);
// 2. 注册冒泡阶段监听器 (useCapture = false, 默认)
parent.addEventListener("click", () => log("Parent", "Bubble"));
child.addEventListener("click", () => log("Child", "Bubble"));
// 3. 点击 "儿子" (child)

📊 输出结果

当你点击 <div id="child"> 时,控制台输出顺序如下:

  1. Grandparent - Capture (捕获:从外向内)
  2. Parent - Capture (捕获:从外向内)
  3. Child - Bubble (目标:通常视为冒泡的一部分,或者单独的目标阶段)
  4. Parent - Bubble (冒泡:从内向外)

注意grandparent 没有注册冒泡监听器,所以最后没有输出 Grandparent - Bubble

4. 🛑 如何阻止事件传播?

有时候,我们不希望事件继续传播(例如:点击弹窗内部不希望关闭弹窗,但点击背景要关闭)。

方法一:event.stopPropagation()

作用:停止事件在 DOM 树中的进一步传播(既阻止后续的捕获,也阻止后续的冒泡)。

child.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log("Child clicked, stop!");
});

结果:点击 child 后,只会执行 child 的回调,parentgrandparent 的回调都不会执行。

方法二:event.stopImmediatePropagation()

作用:不仅阻止事件传播,还阻止当前元素上其他相同事件的监听器执行。

child.addEventListener("click", function (event) {
  event.stopImmediatePropagation();
  console.log("Handler 1");
});
child.addEventListener("click", function () {
  console.log("Handler 2"); // 这行永远不会执行
});

❌ 误区:return false

在 jQuery 中,return false 等价于同时调用 stopPropagation()preventDefault()
但在原生 JS 中,return false 无效,必须显式调用上述方法。

5. 🚀 实际应用场景:为什么我们需要它们?

场景一:事件委托(利用冒泡)

这是冒泡最著名的应用。与其给 1000 个列表项分别绑定事件,不如给父元素 <ul> 绑定一个事件,利用冒泡机制统一处理。

// 利用冒泡,只在 ul 上监听
ul.addEventListener("click", function (event) {
  if (event.target.tagName === "LI") {
    console.log("Clicked:", event.target.innerText);
  }
});

场景二:模态框关闭逻辑(混合使用)

常见需求:点击遮罩层关闭弹窗,点击弹窗内容不关闭。

<div id="overlay">
  <div id="modal">内容...</div>
</div>
const overlay = document.getElementById("overlay");
const modal = document.getElementById("modal");
// 1. 点击遮罩层,关闭弹窗(冒泡阶段)
overlay.addEventListener("click", () => {
  console.log("Close Modal");
});
// 2. 点击弹窗内部,阻止冒泡,防止触发 overlay 的点击事件
modal.addEventListener("click", (event) => {
  event.stopPropagation();
  console.log("Modal Content Clicked");
});

场景三:高级拦截(利用捕获)

如果你想在一个事件到达目标之前“拦截”它(例如全局权限检查、日志记录),可以使用捕获阶段。因为捕获发生在冒泡之前,你可以提前终止事件。

// 在捕获阶段拦截所有点击
document.addEventListener(
  "click",
  (event) => {
    if (event.target.disabled) {
      event.stopPropagation(); // 禁止点击禁用元素
      event.preventDefault();
    }
  },
  true,
); // 注意这里是 true

💡 总结

特性事件捕获 (Capturing)事件冒泡 (Bubbling)
传播方向从上到下 (Window -> Target)从下到上 (Target -> Window)
触发时机早期,用于拦截晚期,用于处理
默认行为需设置 useCapture: true默认 (useCapture: false)
主要用途事件拦截、全局监听事件委托、常规交互

🚀 博主寄语
事件流就像水的流动,既有从天而降的雨(捕获),也有汇聚成河向上的蒸汽(冒泡)。

记住三个关键点

  1. 顺序:先捕获,后目标,再冒泡。
  2. 委托:利用冒泡实现高性能的事件代理。
  3. 阻止:使用 stopPropagation() 控制事件传播范围。

理解了事件流,你就掌握了 DOM 交互的脉搏!

希望这篇文档能帮你彻底搞懂事件捕获与冒泡!

到此这篇关于JavaScript 事件流:从“捕获”到“冒泡”的完整旅程的文章就介绍到这了,更多相关js事件流从捕获到冒泡内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文