JavaScript 處理 DOM 事件上的獲取和冒泡,實務上滿常用到的觀念,可以透過這方式解決一些麻煩問題,例如: popup 視窗的關閉、內外層 DOM 互動關係,另外事件獲取、冒泡也幾乎是面試必考題。

DOM 一般事件綁定

下面的例子,我 container 先綁定 click 事件,再綁定 first,各自彈出自己的 id 名稱,那哪個會先 alert 出來。

<div id="container">container <ul id="list">ist <li id="first">number 1</li> </ul> </div> ... <script> document.getElementById('container').addEventListener('click',function(e){ alert(this.id); }); document.getElementById('first').addEventListener('click',function(e){ alert(this.id); }); </script>

會是 first 先彈跳出來,因為綁定事件順序並不是代表執行順序,單純只是哪個 DOM 先綁定事件監聽,實際執行序還是要依照 DOM 父子關係判定,除非是綁定到同個 DOM 上,才會依照先後綁定順序執行。

事件傳遞 Capture Bubble

我們可以在 addEventListener(‘click’,function(){}, true),來決定 useCapture 參數的 boolean,預設沒帶會是設為 false,當 usecaptue 為 true 時,事件觸發會先經由 DOM tree 一路往子層到目標為止,之後再冒泡上去父層,這樣一個完整的流程就是事件獲取與冒泡。

最重要就是我們有辦法阻止事件獲取冒泡的傳遞,利用 event.stopPropagation function,就可以阻止事件往後傳遞。

另外 event 還有提供物件 eventPhase,會回傳 0~4 的數值,讓我們可以清楚知道這個事件到什麼階段。

eventPhase: 0 沒有事件 eventPhase: 1 獲取階段,會以物件父層一直到最高開始執行,最頂端會是Window, 再來Document,Html,一直到目標為止。 eventPhase: 2 目標階段,這代表事件執行到目標 eventPhase: 3 冒泡階段,會由目標物件的第一層父層開始,一路往上到最頂端window為止。

EventListener usecapture MDN

JavaScript dompass
JavaScript dompass

// get All elements const nodeList= [...document.querySelectorAll('*')]; // add All elements eventListener nodeList.forEach(elem =>{ // use true is for capture elem.addEventListener("click", e => alert(`capture ${elem.tagName} phase:${e.eventPhase}`) , true ); // default false is for bubble elem.addEventListener("click", e => alert(`bubble: ${elem.tagName} phase:${e.eventPhase}`) ); });

簡易獲取&冒泡

MDN eventphase flow (MDN 教學)

Capture Bubble 實用範例

這邊我們用最經典的 popup 例子,需求是要點擊按鈕 click 會讓 popup 顯示的,但在這邊我們希望 popup 內部點擊不會關閉 popup,但是點外部隨便空間會關閉 popup。

首先監聽 body 點擊會關閉 popup,再來監聽 openPop 按鈕點擊讓 popup 打開,這邊還多下了 e.stopPropagation();,防止我點擊 popup 打開觸發 body 點擊被關閉,因為這樣可以中斷點擊 popup 往上冒泡觸發 body 事件。

再來是 popup 本身需要能點選內部內容,同樣我們也對 popup 使用 e.stopPropagation(); ,讓我們可以點擊 popup 裡面的內容、按鈕。

<button id="openPop">open popup</button> <div id="text"></div> // fake wording <div id="popup" class=""> <span id="closePop">x</span> <div> This is popup <button onclick="alert('hello')">Alert Button</button> </div> </div> ... const popup = document.getElementById('popup'); document.getElementById('openPop').addEventListener('click',(e)=>{ e.stopPropagation(); popup.classList.add("active"); }); document.body.addEventListener('click',()=>{ close(); }); popup.addEventListener('click',(e)=>{ e.stopPropagation(); }); document.getElementById('closePop').addEventListener('click',()=>{ close(); }); function close () { popup.classList.remove('active'); }

心得

這觀念真心覺得實用,很多疑難雜症可以處理,或是可以少綁一些 eventlistener,利用父層子層獲取冒泡關係,搭配 stopPropagation。