addEventListener 劫持
本章中我们将学习到如何使用 addEventListener
劫持。
背景:捕获与冒泡阶段
假 设现在有一个 html
<div class="div1">
<div class="div2">
<div class="div3"></div>
</div>
</div>
当我们点击 div3
的时候,会如下进行流程:
-
依次触发
div1
>div2
中捕获监听函数 -
触发
div3
的捕获和冒泡监听,此时不再区分点击元素的捕获和监听,通常按addEventListener
调用的先后顺序处理 -
依次触发
div2
>div1
中冒泡监听函数
addEventListener
使用
addEventListener
劫持通常用于解决网页对于外部状态的检测,如视频检测到最小化页面就不播放,鼠标离开固定区域就会提示等等。
这个时候我们可以利用 addEventListener
劫持对特定的监听事件实现过滤,从而使对应的功能失效或监听触发等等。
-
监听方式一般有两种,
- 通过 onClick 函数等触发调用
- 使用
addEventListener
进行监听
-
目前网页主流使用
addEventListener
,其原因在于:on
系列的监听函数相对性能较差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
触发函数,当我们传入的事件监听类型被网页监听到,网页就会调用这个函数
-
useCapture
和options
-
useCapture
布尔值,为
true
时,在捕获阶段触发回调函数,为false
时,在冒泡阶段触发回调函数 -
options
options
有四个参数,capture
,once
,passive
和signal
参数 说明 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
而是调用后立即停止,不再执行后续的同级同阶段的监听器。