[学习笔记] JavaScript - DOM的事件传递机制 : Capture and Bubble

本篇内容参考连结
TechBridge 技术共笔部落格

Capture and Bubble

Definition

假如网页做久了, 一定会用到下面几个关键字的函式 addEventListener, preventDefaultstopPropagation. 而这两个(缺少哪一个之后会揭晓)使用的方式将会跟今天的主题有很大的关係.简单来说, 就是要讲述事件触发后, 在各个dom物件传递的顺序.

Event Phase

这个机制主要分为三个部分, 依照顺序分别为

Capturing Phase : 捕捉阶段, 为事件触发时的初始阶段At Target Phase : 目标阶段, 为事件触发的终点Bubbling Phase : 冒泡阶段, 为事件的最后阶段

可以先参考以下的图来理解
树状图

首先要知道整个HTML的架构会像是一个树状图, 然后当你点击了<table>里面的其中一个<td>, 事件并不是从<td>开始触发和传递, 而是从最上层Window开始一步一步传下来直到目标, 而这个阶段就是CAPTURING_PHASE. 当事件到达目标<td>后, 会进入下一个阶段AT_TARGET. 然后再慢慢地往回飘上去(因为是Bubble), 这个阶段就是BUBBLING_PHASE. (口诀 : 先捕获, 在冒泡)

addEventListener

而JS里面聆听事件, 并做callback的函式, 就是addEventListener. 其实他还有第三个参数, true 代表添加这个callback到捕获阶段, 而false则是加到冒泡阶段.

事件传递

借用上面这一张图, 写了一个测试範例

eventPhase : 事件的阶段
1: Capturing
2: At Target
3: Bubbleing

<!DOCTYPE html><html>    <body>        <ul id="ul_item">            <li id="li_item">                <a id="a_link">trigger Here</a>            </li>        </ul>        <script>            const ul_item = document.getElementById("ul_item");            const li_item = document.getElementById("li_item");            const a_link = document.getElementById("a_link");            // ul 的捕获            ul_item.addEventListener('click', (e) => {              console.log('ul capturing', e.eventPhase);            }, true)            // ul 的冒泡            ul_item.addEventListener('click', (e) => {              console.log('ul bubbling', e.eventPhase);            }, false)            // li 的捕获            li_item.addEventListener('click', (e) => {              console.log('li capturing', e.eventPhase);            }, true)            // li 的冒泡            li_item.addEventListener('click', (e) => {              console.log('li bubbling', e.eventPhase);            }, false)            // a 的捕获            a_link.addEventListener('click', (e) => {              console.log('a capturing', e.eventPhase);            }, true)            // a 的冒泡            a_link.addEventListener('click', (e) => {              console.log('a bubbling', e.eventPhase);            }, false)        </script>    </body></html>

当点击#a_link时, 结果得到 :
结果

从範例中就可以看到实际运行状况, 不管顺序如何改变, 都会符合先捕捉后冒泡的顺序.

Target Phase

再参考文件中有提到另一个测试, 当在Target Element, 把Capture listener和Bubble listener的编写顺序颠倒, 会得到先输出 Bubble listener, 这是因为都在Target Phase, 所有就没有捕捉或冒泡的关係, 从而根据加入Macrotask的顺序来做输出. 然后也是根据不同的浏览器会有不一样的输出.

在新版的 Chrome 似乎更改了这个行为,请参考:Chrome 89 更新事件触发顺序,导致99%的文章都错了(包括MDN)
在Target Phase, 也会依照先捕捉后冒泡的顺序来输出.

stopPropagation vs stopImmediatePropagation

stopPropagation() 从字面上的意思就可以知道事件到了这个listener就会停止传递到下一个节点, 但在同一个element不同listener还是会被触发. 若想让同一层级的listner不被执行,则可改用stopImmediatePropagation();

preventDefault

这个函式则仅是取消Dom的预设行为 (例如: 像是我们点击超连结<a>,他的预设行为是开新分页或跳转, 当listener添加了 preventDefault() 后, 则就会取消此行为), 并不会影响事件的传递, 但会影响之后节点的预设行为, 并一併取消(例如: 当在母节点中, 添加preventDefault() 后, 等事件传递到之后的超连结, 也都会失效).所以上面三个函式中, 就是他与事件传递机制无关.


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章