DOM事件模型,你真的理解吗?

650 阅读3分钟

事件监听器 addEventListener

在JavaScript中,addEventListener用于向指定元素添加事件监听器。这个方法有三个参数,你是否都清楚呢?它可以更精细地控制 listener 的触发阶段。

addEventListener(type, listener, options?);
addEventListener(type, listener, useCapture?);
  • type: 事件类型(如'click'、'mouseover'等)
  • listener: 事件处理函数(即当事件触发时要执行的函数)
  • useCapture: 可选参数,控制事件监听器是在捕获阶段还是冒泡阶段触发,默认 false 即在冒泡阶段触发
  • options: 可选参数
    • capture:触发时机 - true 捕获阶段;false 冒泡阶段(默认 false)
    • once:设置为true,则表示 listener 会在其被调用一次后自动移除(在某些特定背景下使用,可以省去移除事件监听器的逻辑编写)
    • passive: 表示listener是否永远不会调用 preventDefault()(需关注浏览器兼容性问题)
    • signal:该 AbortSignal 的 abort() 方法被调用时(中止请求),监听器会被移除

addEventListener 工作原理

将实现 EventListener 的函数或对象添加到调用它的 EventTarget 上的指定事件类型的事件侦听器列表中。

如果 capture 为 true,则监听器被注册为捕捉事件监听器。如果 capture 为 false,它被注册为普通事件监听器。

addEventListener() 可能被调用多次,在同一个节点上为同一种类型的事件注册多个事件句柄。但要注意,DOM 不能确定多个事件句柄被调用的顺序。

如果要绑定的函数或对象已经被添加到列表中,该函数或对象不会被再次添加。

事件流

在DOM事件模型中,事件流分为三个阶段:

  • 捕获阶段(Capturing Phase):事件从根元素向目标元素传播
  • 目标阶段(Target Phase):事件到达目标元素
  • 冒泡阶段(Bubbling Phase):事件从目标元素向根元素传播

capture

举个例子,展示如何使用capture参数以及事件触发顺序

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>实践</title>
    <style>
      .container {
        padding: 50px;
        border: 1px solid blue;
      }
      .child {
        padding: 50px;
        border: 1px solid red;
      }
    </style>
  </head>
  <body>
    <div class="container">
      Container
      <div class="child">Child</div>
    </div>
    <script>
      const container = document.querySelector('.container');
      const child = document.querySelector('.child');
      // 捕获阶段监听container的点击事件
      container.addEventListener(
        'click',
        function () {
          console.log('Container clicked capture');
        },
        true,
      );
      // 冒泡阶段监听container的点击事件
      container.addEventListener(
        'click',
        function () {
          console.log('Container clicked bubble');
        },
        false,
      );
      // 捕获阶段监听child的点击事件
      child.addEventListener(
        'click',
        function () {
          console.log('Child clicked capture');
        },
        true,
      );
      // 冒泡阶段监听child的点击事件
      child.addEventListener(
        'click',
        function () {
          console.log('Child clicked bubble');
        },
        false,
      );
    </script>
  </body>
</html>

以上代码中为 container 和 child 元素分别添加了两个点击事件监听器,一个在捕获阶段,一个在冒泡阶段,那么,点击 container 元素和点击 child 元素,分别会触发哪些事件以及触发顺序会是怎样的呢?

  1. 当你点击Container元素时,事件会按照以下顺序触发:
  • Container clicked (capture)(捕获阶段)
  • Container clicked (bubble)(冒泡阶段)
  1. 当你点击child元素时,事件会按照以下顺序触发:
  • Container clicked (capture)(捕获阶段)
  • Child clicked (capture)(捕获阶段)
  • Child clicked (bubble)(冒泡阶段)
  • Container clicked (bubble)(冒泡阶段)

那如果在某些特定场景下,我想点击Container元素或者点击child元素时都只触发 Container clicked (capture)事件,该如何控制?

通过这种方式,我们可以控制事件监听器是在捕获阶段还是冒泡阶段触发,从而更灵活地处理事件。

相关参考

MDN-addEventListener