◆

JavaScript addEventListener 事件监听与冒泡捕获详解

2021-02-02 01:370

eventTarget

  • EventTarget 是一个 DOM 接口,由可以接收事件、并且可以创建侦听器的对象实现。

addEventListener

  • 将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
  • 事件目标可以是一个文档上的元素 Element,Document 和 Window 或者任何其他支持事件的对象 (比如 XMLHttpRequest)。
  • addEventListener() 的工作原理是将实现 EventListener 的函数或对象添加到调用它的 EventTarget 上的指定事件类型的事件侦听器列表中。
  • 语法
    • target.addEventListener(type, listener, options);
    • target.addEventListener(type, listener, useCapture);
    • type:表示监听事件类型的字符串。
    • listener
      • 当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。 listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。
    • options 可选。一个指定有关 listener 属性的可选参数对象。可用的选项如下:
      • capture: Boolean,表示 listener 是否使用捕获事件,意味着事件触发将从根元素到目标节点。
      • once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
      • passive: Boolean,设置为 true 时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
    • useCapture: 可选 Boolean,在 DOM 树中,注册了 listener 的元素,是否要先于它下面的 EventTarget,调用该 listener。 当 useCapture(设为 true) 时,沿着 DOM 树向上冒泡的事件。如果没有指定, useCapture 默认为 false 。
  • 返回值 undefined.

事件冒泡捕获

DOM 事件流

  • 事件流所描述的就是从页面中接受事件的顺序。
  • DOM 事件流存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
  • 浏览器在为这个当前页面与用户做交互的过程中,比如我点击了这个鼠标左键,这个左键是怎么传到页面上。 还有怎么响应的。事件流分三个阶段,第一阶段是捕获,第二阶段是目标阶段, 比如点击的这个按钮,这个按钮就是目标阶段,事件通过捕获到达目标元素,就到达了目标阶段, 第三个阶段是冒泡阶段,从目标元素再上传到 window 对象,就是冒泡的过程。

事件传播——冒泡与捕获

默认情况下,事件使用冒泡事件流,不使用捕获事件流。然而,在 Firefox 和 Safari 里, 你可以显式的指定使用捕获事件流,方法是在注册事件时传入 useCapture 参数,将这个参数设为 true。

冒泡事件流

  • 事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。
  • 阻止事件冒泡可以通过调用事件对象上的 stopPropagation() 方法, 在 Internet Explorer 里可以通过设置事件对象的 cancelBubble 属性为 true。
  • 如果不停止事件的传播,事件将一直通过 DOM 冒泡直至到达文档根。
  • DOM 事件冒泡的具体流程:第一个接收的是目标元素——第二个接收的是...(子级--父级,按照 html 结构一层一层往上传) ——然后接收的是 body 标签—— html 标签——document 对象——最后一个接收的是 window 对象。

捕获事件流

  • 事件的处理将从 DOM 层次的根开始,而不是从触发事件的目标元素开始, 事件被从目标元素的所有祖先元素依次往下传递。

  • 在这个过程中,事件会被从文档根到事件目标元素之间各个继承派生的元素所捕获,

  • 如果事件监听器在被注册时设置了 useCapture 属性为 true, 那么它们可以被分派给这期间的任何元素以对事件做出处理; 否则,事件会被接着传递给派生元素路径上的下一元素,直至目标元素。

  • 事件到达目标元素后,它会接着通过 DOM 节点再进行冒泡。

  • DOM 事件捕获的具体流程:捕获是从上到下,具体第一个真正接收的是 window(对象)——第二个接收的是 document(对象)—— 第三个接收的是 html 标签(怎么获取 html 标签> document.documentElement)——第四个接收的是 body(document.body) ——......(父级--子级,剩下的就是按照普通的 html 结构一层一层往下传)——最后到达目标元素。

示例

默认无参数

::: demo [vanilla]

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div
      id="div1"
      style="color: white;background: red;width: 240px;height: 240px"
    >
      1
      <div
        id="div2"
        style="color: white;background: green;width: 200px;height: 200px"
      >
        2
        <div
          id="div3"
          style="color: white;background: blue;width: 150px;height: 150px"
        >
          3
          <div
            id="div4"
            style="color: white;background: black;width: 100px;height: 100px"
          >
            4
          </div>
        </div>
      </div>
    </div>
    <script>
      const div1 = document.getElementById('div1');
      const div2 = document.getElementById('div2');
      const div3 = document.getElementById('div3');
      const div4 = document.getElementById('div4');
      div1.addEventListener('click', function (e) {
        alert('div1的监听事件输出:' + 1);
      });
      div2.addEventListener('click', function (e) {
        alert('div2的监听事件输出:' + 2);
      });
      div3.addEventListener('click', function (e) {
        alert('div3的监听事件输出:' + 3);
      });
      div4.addEventListener('click', function (e) {
        alert('div4的监听事件输出:' + 4);
      });
    </script>
  </body>
</html>
{
  "show": false
}

:::

useCapture

tip

与无参数等同

useCapture 指定事件是否在捕获或冒泡阶段执行。

  • true: 事件在捕获阶段执行
  • false: 默认。事件在冒泡阶段执行
div1.addEventListener(
  'click',
  function (e) {
    alert('div1的监听事件输出:' + 1);
  },
  false,
);
div2.addEventListener(
  'click',
  function (e) {
    alert('div2的监听事件输出:' + 2);
  },
  false,
);
div3.addEventListener(
  'click',
  function (e) {
    alert('div3的监听事件输出:' + 3);
  },
  false,
);
div4.addEventListener(
  'click',
  function (e) {
    alert('div4的监听事件输出:' + 4);
  },
  false,
);

capture: false

tip

与无参数和 useCapture: false 等同

div1.addEventListener(
  'click',
  function (e) {
    console.log('div1的监听事件输出:', 1);
  },
  { capture: false },
);
div2.addEventListener(
  'click',
  function (e) {
    console.log('div2的监听事件输出:', 2);
    event.stopPropagation();
  },
  { capture: true },
);
div3.addEventListener(
  'click',
  function (e) {
    console.log('div3的监听事件输出:', 3);
  },
  { capture: false },
);
div4.addEventListener(
  'click',
  function (e) {
    console.log('div4的监听事件输出:', 4);
  },
  { capture: false },
);

阻止事件冒泡或事件捕获

只会有目标元素触发,不会冒泡到上一级元素上 ::: demo [vanilla]

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div
      id="div11"
      style="color: white;background: red;width: 240px;height: 240px"
    >
      11
      <div
        id="div12"
        style="color: white;background: green;width: 200px;height: 200px"
      >
        12
        <div
          id="div13"
          style="color: white;background: blue;width: 150px;height: 150px"
        >
          13
          <div
            id="div14"
            style="color: white;background: black;width: 100px;height: 100px"
          >
            14
          </div>
        </div>
      </div>
    </div>
    <script>
      /**
       * 当在某一个逻辑中添加 event.stopPropagation(); 会阻止当前的事件向下或向上传播
       * 如:在冒泡事件流当中,当我们在 div14 中添加 event.stopPropagation() 时,
       * 点击 div14 只会输出 14 因为在 div14 中 event.stopPropagation() 阻止了事件向上传播
       *
       * 在 div12 中添加。点击div4 会输出 14,13,12 因为在 div12 中事件被组件向上传播
       */
      const div11 = document.getElementById('div11');
      const div12 = document.getElementById('div12');
      const div13 = document.getElementById('div13');
      const div14 = document.getElementById('div14');
      div11.addEventListener('click', function (e) {
        alert('div11的监听事件输出:' + 11);
      });
      div12.addEventListener('click', function (e) {
        e.stopPropagation();
        alert('div12的监听事件输出:' + 12);
      });
      div13.addEventListener('click', function (e) {
        alert('div13的监听事件输出:' + 13);
      });
      div14.addEventListener('click', function (e) {
        alert('div14的监听事件输出:' + 14);
        e.stopPropagation();
      });
    </script>
  </body>
</html>
{
  "show": false
}

:::

捕获事件流示例

useCapture: true

/**
 * useCapture 指定事件是否在捕获或冒泡阶段执行。
 * true - 事件句柄在捕获阶段执行
 * false- false- 默认。事件句柄在冒泡阶段执行
 */
div1.addEventListener(
  'click',
  function (e) {
    console.log('div1的监听事件输出:', 1);
  },
  true,
);
div2.addEventListener(
  'click',
  function (e) {
    console.log('div2的监听事件输出:', 2);
  },
  true,
);
div3.addEventListener(
  'click',
  function (e) {
    console.log('div3的监听事件输出:', 3);
  },
  true,
);
div4.addEventListener(
  'click',
  function (e) {
    console.log('div4的监听事件输出:', 4);
  },
  true,
);

或者 capture:true

div1.addEventListener(
  'click',
  function (e) {
    console.log('div1的监听事件输出:', 1);
  },
  { capture: true },
);
div2.addEventListener(
  'click',
  function (e) {
    console.log('div2的监听事件输出:', 2);
  },
  { capture: true },
);
div3.addEventListener(
  'click',
  function (e) {
    console.log('div3的监听事件输出:', 3);
  },
  { capture: true },
);
div4.addEventListener(
  'click',
  function (e) {
    console.log('div4的监听事件输出:', 4);
  },
  { capture: true },
);

stopImmediatePropagation

event.stopImmediatePropagation 与 event.stopPropagation() 对阻止事件的冒泡与捕获有相同的作用 区别是当给 div2 增加两个 addEventListener click 事件时, 给 div2 的第一个 addEventListener 事件改用 stopImmediatePropagation 时,输出 4,3,2, 而使用 stopPropagation 会输出 4,3,2,22

/**
 * event.stopImmediatePropagation 与 event.stopPropagation() 对阻止事件的冒泡与捕获有相同的作用
 * 区别是当给 div2 增加两个 addEventListener click 事件时,
 * 给 div2 的第一个 addEventListener 事件改用 stopImmediatePropagation 时,输出 4,3,2,
 * 而使用 stopPropagation 会输出  4,3,2,22
 */
div1.addEventListener('click', function (e) {
  console.log('div1的监听事件输出:', 1);
  // event.stopPropagation();
});
div2.addEventListener('click', function (e) {
  console.log('div2的监听事件输出:', 2);
  // event.stopPropagation();
  event.stopImmediatePropagation();
});
div2.addEventListener('click', function (e) {
  console.log('div2另一个的监听事件输出:', 22);
});
div3.addEventListener('click', function (e) {
  console.log('div3的监听事件输出:', 3);
  // event.stopPropagation();
});
div4.addEventListener('click', function (e) {
  console.log('div4的监听事件输出:', 4);
  // event.stopPropagation();
});

once

表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。

/**
 * 如下所示给 div1 div2 添加属性 once:true,div3 div4 添加属性 once:false
 * @example 当首次点击 div4 由于默认是冒泡事件流,div4 的事件会先触发,接着 div3 div2 div1 依次触发
 *          打印 4,3,2,1,然后我们再点击 div4 由于 div1 div2 的 once 为 true
 *          所以只会输出 4,3 表示当添加 once 为 true 的 listener 只会触发一次
 */
div1.addEventListener(
  'click',
  function (e) {
    console.log('div1的监听事件输出:', 1);
  },
  { once: true },
);
div2.addEventListener(
  'click',
  function (e) {
    console.log('div2的监听事件输出:', 2);
  },
  { once: true },
);
div3.addEventListener(
  'click',
  function (e) {
    console.log('div3的监听事件输出:', 3);
  },
  { once: false },
);
div4.addEventListener(
  'click',
  function (e) {
    console.log('div4的监听事件输出:', 4);
  },
  { once: false },
);

preventDefault & passive

  • passive: 设置为 true 时,表示 listener 永远不会调用 preventDefault()。 如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
  • event.preventDefault: 告诉浏览器如果此事件没有被显式处理,它默认的动作也不应该照常执行。 此事件还是继续传播,除非碰到事件侦听器调用 stopPropagation() 或 stopImmediatePropagation(),才停止传播。 ::: demo [vanilla]
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    这是 checkbox:
    <input type="checkbox" id="id-checkbox" />
    <br />
    这是 a 标签:
    <a id="custormUrlOne" onclick="" href="http://www.baidu.com">
      跳转百度 event.preventDefault() passive: false
    </a>
    <br />
    这是 a 标签:
    <a id="custormUrlTwo" onclick="" href="http://www.baidu.com">
      跳转百度 event.preventDefault() passive: true
    </a>

    <div id="output-box"></div>
    <script>
      document.querySelector('#id-checkbox').addEventListener(
        'click',
        function (event) {
          document.getElementById('output-box').innerHTML +=
            "Sorry! <code>preventDefault()</code> won't let you check this!<br>";
          event.preventDefault();
        },
        false,
      );

      document.querySelector('#custormUrlOne').addEventListener(
        'click',
        function (event) {
          console.log('点击了 a 标签');
          event.preventDefault();
        },
        { passive: false },
      );

      document.querySelector('#custormUrlTwo').addEventListener(
        'click',
        function (event) {
          console.log('点击了 a 标签');
          event.preventDefault();
        },
        { passive: true },
      );
    </script>
  </body>
</html>
{
  "show": true
}

:::

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEJavaScript addEventListener 事件监听与冒泡捕获详解加载中