跳到主要内容

addEventListener 劫持

本章中我们将学习到如何使用 addEventListener 劫持。

背景:捕获与冒泡阶段

假设现在有一个 html

<div class="div1">
<div class="div2">
<div class="div3"></div>
</div>
</div>

当我们点击 div3 的时候,会如下进行流程:

  1. 依次触发 div1 > div2 中捕获监听函数

  2. 触发 div3 的捕获和冒泡监听,此时不再区分点击元素的捕获和监听,通常按 addEventListener 调用的先后顺序处理

  3. 依次触发 div2 > div1 中冒泡监听函数

addEventListener 使用

addEventListener 劫持通常用于解决网页对于外部状态的检测,如视频检测到最小化页面就不播放,鼠标离开固定区域就会提示等等。

这个时候我们可以利用 addEventListener 劫持对特定的监听事件实现过滤,从而使对应的功能失效或监听触发等等。

补充
  • 监听方式一般有两种,

    1. 通过 onClick 函数等触发调用
    2. 使用 addEventListener 进行监听
  • 目前网页主流使用 addEventListener,其原因在于:

    1. on 系列的监听函数相对性能较差
    2. addEventListener 支持添加多个函数

关于 addEventListener 我们可以查询MDN 文档

addEventListener 来自 EventTarget 这个对象下的原型中,通常来说我们的 dom 元素等也都是继承自这个对象的原型链

所以我们可以写出以下劫持代码

const oldEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (...args) {
console.log("addEventListener Hook", this, ...args);
return oldEventListener.call(this, ...args);
};

addEventListener 的语法

addEventListener 一共有三种调用方式

addEventListener(type, listener);
addEventListener(type, listener, options);
addEventListener(type, listener, useCapture);

addEventListener 的参数

  • type

    事件监听类型

    关于事件我们可以在菜鸟教程查询到

    提示

    addEventListener 使用不需要带 on 前缀

  • listener

    触发函数,当我们传入的事件监听类型被网页监听到,网页就会调用这个函数

  • useCaptureoptions

    • useCapture

      布尔值,为 true 时,在捕获阶段触发回调函数,为 false 时,在冒泡阶段触发回调函数

    • options

      options 有四个参数,captureoncepassivesignal

      参数说明
      capture布尔值,在捕获阶段触发回调函数,该选项与 useCapture 等价
      once布尔值,为 true 时,在调用一次后自动移除
      passive布尔值,为 true 时,承诺回调函数不会调用 preventDefault,这样确保了回调函数的逻辑一定不阻止默认操作,从而无需等待回调函数告知是否阻止操作即可执行,提升网页的性能,改善体验。
      signal传入一个信号对象,等信号对象调用 abort 函数后,addEventListener 将被自动移除

listener 的一些常用函数

listener 作为一个回调函数,当监听的对应事件触发时会调用该函数,同时传入一个 Event 对象,具体的内容可以参考MDN,我们这里仅挑出一些常用的函数进行讲解。

preventDefault

取消默认行为的执行,如一个 a 标签,点击后会自动跳转网页,如果我们调用如下代码,即不会再跳转网页。

提示

并非所有活动都可以取消。可以通过 cancelable 属性来确定事件是否可取消。

document.querySelector("a").addEventListener("click", (event) => {
event.preventDefault();
});

stopPropagation

阻止后续传播。依然以之前的html为例:

<div class="div1">
<div class="div2">
<div class="div3"></div>
</div>
</div>

从捕获到冒泡的顺序依次是

div1 捕获 > div2 捕获 > div3 捕获和冒泡 > div2 冒泡 > div1 冒泡

这个时候如果在 div2 捕获时调用 stopPropagation,在 div2 捕获的监听器全部执行完毕后,后续的div3捕获和冒泡div2冒泡div1冒泡都将不再执行

注意

在当前元素调用 stopPropagation 后,依然会执行完该元素的所有监听器后停止, 例如:

div2 设置了两个捕获监听器,

在第一个调用了 stopPropagation 后,第二个监听器依然会执行完毕。

stopImmediatePropagation

如果你理解了 stopPropagation,那么 stopImmediatePropagation 也很好理解,即使调用了 stopPropagation,同级同阶段的监听器依然会全部执行完毕后才停止,而 stopImmediatePropagation 而是调用后立即停止,不再执行后续的同级同阶段的监听器。