你确定你知道事件在 JavaScript 中是如何传播的吗?

2025-05-24

你确定你知道事件在 JavaScript 中是如何传播的吗?

事件在 Web 编程中无处不在——输入变化、鼠标移动、按钮点击和页面滚动都是事件。这些操作由系统生成,您可以通过注册事件监听器以任何您喜欢的方式响应它们。
这为用户带来了交互式体验。了解现代 Web 浏览器中事件模型的工作原理,可以帮助您构建强大的 UI 交互。如果处理不当,就会出现 bug。

本文旨在阐述 W3C 事件模型中事件传播机制的一些基础知识。所有现代浏览器都实现了该模型。

让我们开始吧⏰。


事件传播

想象一下,如果我们有两个 HTML 元素,element1element2,其中element2是element1的子元素,如下图所示:

带有点击处理程序的嵌套元素

我们给它们都添加点击处理程序,如下所示:

element1.addEventListener('click', () => console.log('element1 is clicked'));
element2.addEventListener('click', () => console.log('element2 is clicked'));
Enter fullscreen mode Exit fullscreen mode

你认为点击element2时会输出什么?🤔

答案是element2 is clicked,其次是element1 is clicked。这种现象被称为事件冒泡,它是W3C事件模型的核心部分。

在事件冒泡中,最内层的目标元素首先处理事件,然后在 DOM 树中向上冒泡,寻找具有注册事件处理程序的其他祖先元素。

💡 在事件冒泡中,最内层的目标元素首先处理事件,然后它在 DOM 树中冒泡

现在,有趣的是,事件流并非像你可能认为的那样是单向的。W3C 事件模型中的事件流机制是双向的。惊喜!😯。

在使用 React 等框架时,我们主要处理事件冒泡,而从未考虑过另一个阶段,即事件捕获

💡 事件冒泡只是硬币的一面;事件捕获是另一面。

在事件捕获阶段,事件首先被捕获,直到到达目标元素(event.target)。作为 Web 开发人员,您可以在此阶段通过将其设置为方法true中的第三个参数来注册事件处理程序addEventListener

// With addEventListener() method, you can specify the event phase by using `useCapture` parameter.
addEventListener(event, handler, useCapture);
Enter fullscreen mode Exit fullscreen mode

默认情况下,它为false,表示我们在冒泡阶段注册此事件。
让我们修改一下上面的示例,以便更好地理解这一点。

// Setting "true" as the last argument to `addEventListener` will register the event handler in the capturing phase.
element1.addEventListener('click', () => console.log('element1 is clicked'), true);

// Whereas, omitting or setting "false" would register the event handler in the bubbing phase. 
element2.addEventListener('click', () => console.log('element2 is clicked')));
Enter fullscreen mode Exit fullscreen mode

我们添加了trueforuseCapture参数,表示我们将在捕获阶段为element1注册事件处理程序。对于element2,省略或传递该参数false都将在冒泡阶段注册事件处理程序。

现在,如果你点击element2,你会看到element1 is clicked先打印 ,然后打印element2 is clicked。这就是捕获阶段的实际操作。

💡 在事件捕获阶段,首先捕获事件,直到到达目标元素

下面的图表可以帮助您轻松地将其形象化:

演示 W3C 事件模型中的事件流

事件流顺序为:

  1. “click”事件在捕获阶段开始。它会查找element2的任何祖先元素是否onClick捕获阶段的事件处理程序。
  2. 该事件找到元素1,并调用处理程序,打印出element1 is clicked
  3. 事件向下流向目标元素本身(element2),并寻找路径上的任何其他元素。但没有找到捕获阶段的事件处理程序。
  4. 到达element2后,冒泡阶段开始并执行在element2上注册的事件处理程序,打印element2 is clicked
  5. 事件再次向上传播,寻找目标元素(element2)的祖先元素,该祖先元素具有冒泡阶段的事件处理程序。然而,目标元素的祖先元素并没有找到,所以什么也没有发生。

所以,这里要记住的关键点是,整个事件流是事件捕获阶段事件冒泡阶段的组合。作为事件处理程序的作者,您可以指定在哪个阶段注册事件处理程序。🧐

掌握了这些新知识后,我们来回顾一下第一个例子,并尝试分析为什么输出是倒序的。这里再次引用第一个例子,这样你就不会制造scroll事件了 😛

element1.addEventListener('click', () => console.log('element1 is clicked'));
element2.addEventListener('click', () => console.log('element2 is clicked'));
Enter fullscreen mode Exit fullscreen mode

省略该useCapture值会在冒泡阶段为两个元素注册事件处理程序。点击element2 时,事件流顺序如下:

  1. "click" 事件在捕获阶段开始。它会查找 element2 的任何祖先元素是否有用于onClick捕获阶段的事件处理程序,但未找到。
  2. 事件向下传播到目标元素本身(element2)。到达 element2 后,冒泡阶段开始,执行在 element2 上注册的事件处理程序,并打印element2 is clicked
  3. 事件再次向上传播,寻找目标元素(element2)的任何祖先,该祖先具有冒泡阶段的事件处理程序。
  4. 此事件在element1上找到一个。处理程序被执行并element1 is clicked打印出来。

另一件有趣的事情是注销事件的eventPhase属性。这有助于你直观地了解当前正在执行事件的哪个阶段。

element1.addEventListener("click", (event) =>
  console.log("element1 is clicked", { eventPhase: event.eventPhase })
);
Enter fullscreen mode Exit fullscreen mode

如果您想试用,可以参考Codepen 上的演示。或者,您也可以将下面的代码片段粘贴到浏览器中亲自查看。

const element1 = document.createElement("div");
const element2 = document.createElement("div");

// element1: Registering event handler for the capturing phase
element1.addEventListener(
  "click",
  () => console.log("element1 is clicked"),
  true
);

// element2: Registering event handler for the bubbling phase
element2.addEventListener("click", () => console.log("element2 is clicked"));

element1.appendChild(element2);

// clicking the element2
element2.click();
Enter fullscreen mode Exit fullscreen mode

停止事件传播

如果您希望在任何阶段阻止当前事件的进一步传播,您可以调用对象上可用的stopPropagation方法Event

因此,这意味着在元素1的event.stopPropagation()事件处理程序中(在捕获阶段)调用它,将会停止传播。即使你现在点击元素2,它也不会调用它的处理程序。

以下示例表明:

// Preventing the propagation of the current event inside the handler
element1.addEventListener(
  "click",
  (event) => {
    event.stopPropagation();
    console.log("element1 is clicked");
  },
  true
);
// The event handler for the element2 will not be invoked.
element2.addEventListener('click', () => console.log('element2 is clicked'));
Enter fullscreen mode Exit fullscreen mode

请注意,这event.stopPropagation仅会停止传播。但它不会阻止任何默认行为的发生。例如,点击链接仍会被处理。要停止这些行为,您可以使用event.preventDefault()方法。

最后,这里还有另一个很酷的JSbin 演示,如果您愿意尝试一下并了解如何通过 停止事件传播event.stopPropagation

希望这篇文章对你有所帮助,并能给你一些启发。感谢阅读😍


有用的资源:

文章来源:https://dev.to/aman_singh/are-you-sure-you-know-how-event-propagates-in-javascript-2ojn
PREV
精选的新闻通讯列表,提升您的编码技能
NEXT
让你成为更优秀程序员的软技能