文档收录至 web前端知识空间www.nqone.com/…
当点击一个按钮,触发点击事件,页面中事件触发顺序是怎样的呢?
触发流程
一个元素触发事件时, 事件触发流程是这样的, 先从顶部 document 开始向下捕获直到触发事件的目标元素, 然后目标元素事件触发, 接着开始向上冒泡, 直到根元素 document。使用 事件触发后 eventPhase 属性就能查看到当前事件绑定的方法是在哪个阶段触发了。
eventPhase 取值为数字 0、1、2、3,如果想要打印查看结果的时候,查看最准确的值需要直接打印该属性的值。如下是分别对应含义:
- 0:没有事件发生;
- 1:事件捕获阶段触发;
- 2:事件触发目标元素本身触发;
- 3:事件冒泡阶段触发;
如果使用 addEventListener 绑定的该类事件, 第三个方法为 true 时, 在向下捕获的过程中, 对应的事件绑定的方法便会触发。如果为 false, 那么绑定的该类事件只会在 从目标元素向上冒泡时才会触发。
简而言之:事件在页面触发流程是这样的, 顶级元素开始捕获 => 事件触发目标元素 => 向上冒泡至顶级元素
addEventListener
用来给元素绑定一个事件,有三个参数,第一个参数事件名称,第二个参数为绑定事件触发需要执行的方法,第三个 bool 参数用来定义捕获阶段触发(true)还是冒泡阶段触发(false),默认为 false,一般使用时最好写明,以防不同浏览器版本等有差异。
第三个参数还有可能是对象, 如果是对象, 含义如下:
- capture: 表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发
- once: 表示 listener 在添加之后最多只调用一次。如果为 true,listener 会在其被调用之后自动移除
- passive: 设置为 true 时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看使用 passive 改善滚屏性能以了解更多
- signal: 该 AbortSignal 的 abort() 方法被调用时,监听器会被移除
stopPropagation
该方法用来**阻止**同类**事件的进一步传播**,如果是**捕获过程**,将会**终止事件的向下捕获**,如果是冒**泡过程**,将会终**止事件的向上冒泡**。
需要特别注意:因为事件传递流程是**先捕获后冒泡**,所以如果在捕获阶段调用了该方法,那么从顶级向下捕获时,会在对应的位置停止事件传播,之后的 捕获阶段 和 目标事件 以及 冒泡 等均不会再执行,使用时需要注意。
比如 在 document 上 使用 addEventListener 绑定了点击事件,然后调用了 stopPropagation 方法,子元素绑定点击事件时,那么基本上只会触发 document 上的点击事件,其他均不会触发。
详情示例
HTML 代码如下所示,定义了层层元素以及添加类名:
<body>
<div class="out-div">
<button class="out-btn">out</button>
<div class="middle-div">
<button class="middle-btn">middle</button>
<div class="inner-div">
<button class="inner-btn">inner</button>
</div>
</div>
</div>
</body>
<script src="./addEventListener.js"></script>
addEventListener.js 代码如下所示:
// 注意,千万不要直接打印 evt 来查看,因为对象引用只会打印最后的值,要么debugger查看,要么直接打印对应的固定值
document.addEventListener('click', (evt) => {
/** 如果在此处调用,那么页面上只会存在如下的打印 */
// evt.stopPropagation();
console.log('document', evt.eventPhase);
}, true);
const outDiv = document.querySelector('.out-div');
outDiv.addEventListener('click', (evt) => {
console.log('out-div =>', evt.eventPhase);
}, false);
const outBtn = document.querySelector('.out-btn');
outBtn.addEventListener('click', (evt) => {
console.log('out-btn =>', evt.eventPhase);
}, false);
const middleDiv = document.querySelector('.middle-div');
middleDiv.addEventListener('click', (evt) => {
// evt.stopPropagation();
console.log('middle-div =>', evt.eventPhase);
}, true);
const middleBtn = document.querySelector('.middle-btn');
middleBtn.addEventListener('click', (evt) => {
console.log('middle-btn =>', evt.eventPhase);
}, false);
const innerDiv = document.querySelector('.inner-div');
innerDiv.addEventListener('click', (evt) => {
// evt.stopPropagation();
console.log('inner-div =>', evt.eventPhase);
}, false);
const innerBtn = document.querySelector('.inner-btn');
innerBtn.addEventListener('click', (evt) => {
console.log('inner-btn =>', evt.eventPhase);
}, false);
当在页面中点击最内部一个按钮,也就是 类名为 “inner-btn” 的按钮,打印结果如下所示:
从图中可以看出,事件执行是先往目标元素捕获,触发对应的使用捕获的方式监听的事件,到达触发源,在往上冒泡,触发使用冒泡方式绑定的事件。
如果该 demo 在 document 绑定的点击事件中调用 evt.stopPropagation();,及代码中的注释部分打开,那么页面中将只会打印 document 1,其他的均不会触发。