明白事件同時觸發時的先後順序。
先了解可能同時觸發事件的狀況,下面是爺元素、父元素、子元素,一個包一個,祖孫三代齊聚一堂。加了一些 CSS ,因此實際呈現後會像這樣。
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
將其全部添加監聽器,監聽點擊事件,一旦觸發會回饋觸發元素的類別名稱。
const divs = document.querySelectorAll('div');
function logText(e) {
console.log(this.classList.value);
}
divs.forEach(div => div.addEventListener('click', logText);
因為元素們互相重疊,因此點擊最裡面的元素,會同時觸發三個事件。問題來了!究竟哪個事件會先被觸發呢?
答案是 three
、 two
、 one
!猜對了嗎?這是由於事件觸發順序的預設在 冒泡階段(Bubbling)觸發。
是否一頭霧水?首先來看看瀏覽器是如何捕捉事件觸發的吧。
圖片取自 W3C 文件 。
上圖解釋了觸發事件時,事件的觸發流程,這個流程稱為事件流(Event Flow)。觸發事件的最終目標稱為 EventTarget
, 在碰觸到 EventTarget
前,瀏覽器會從最外層元素開始,搜尋每個 EventTarget
的外層元素是否有事件觸發,這個搜尋一直到 EventTarget
為止。這個階段稱作 Capture Phase
,可以想像一直向下鎖定目標那種感覺。
接著碰觸到最終目標,這個階段稱作 Target Phase
。
最後階段稱作 Bubble Phase
,會由最終目標開始往外層元素逐層搜索是否有事件觸發,直至最外層元素為止,可以想像泡泡從水底往上移動。
事件監聽器 addEventListener
的第三個參數為選用參數,可以用來決定事件要在什麼階段觸發。預設是 false
,表示不希望在 Capture
階段觸發,也就是說,事件會在 Bubble
冒泡階段觸發,所以依照示意圖,會從最底層元素開始往外觸發,因此先 three
,接著往父元素尋找,發現也有監聽事件,因此觸發 two
,以此類推最後 one
。
如果第三個參數輸入 true
,事件會在 Capture
階段觸發,因此由外而內尋找監聽器並觸發事件,得到的結果會是 one
、 two
、 three
。
第三個參數也能用物件的方式輸入,像這樣:
divs.forEach(div => div.addEventListener('click', logText, {
capture: false,
// 下面可以設置別的選項
}));
該種方式可以涵蓋其他選項,後續會提到。
有時候元素不可避免會重疊,各元素又有相對應的監聽事件,但我們基於某些操作可能只希望第一個元素觸發,此時可以使用 Event.stopPropagation()
來阻止事件觸發的傳遞。
例如下面程式碼這樣,就能在第一個事件觸發後,瀏覽器就停止 Event Flow 的前進。因此如果第三個參數 false
的狀態,就只會顯示 three
,反之則為 one
。不會有其他兩個數字。
function logText(e) {
console.log(this.classList.value);
e.stopPropagation(); // stop bubbling!
}
最後介紹 once 這個選項,當第三個選項是物件時可以放進屬性內。
divs.forEach(div => div.addEventListener('click', logText, {
once: true,
}));
這是個惡毒的屬性,會在事件觸發後直接將監聽器移除,因此事件只能觸發一次。如果我們想讓某個事件只執行一次,可以使用該屬性。
以上就是 JS30 第二十五篇!
EventTarget.addeventListener
完整程式碼