iT邦幫忙

0

[學習筆記] JavaScript - DOM的事件傳遞機制 : Capture and Bubble

  • 分享至 

  • xImage
  •  

本篇內容參考連結
TechBridge 技術共筆部落格

Capture and Bubble

Definition

假如網頁做久了, 一定會用到下面幾個關鍵字的函式 addEventListener, preventDefaultstopPropagation. 而這兩個(缺少哪一個之後會揭曉)使用的方式將會跟今天的主題有很大的關係.簡單來說, 就是要講述事件觸發後, 在各個dom物件傳遞的順序.

Event Phase

這個機制主要分為三個部分, 依照順序分別為

  1. Capturing Phase : 捕捉階段, 為事件觸發時的初始階段
  2. At Target Phase : 目標階段, 為事件觸發的終點
  3. 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() 後, 等事件傳遞到之後的超連結, 也都會失效).所以上面三個函式中, 就是他與事件傳遞機制無關.


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言