秉著怕被嘴「怎麼連這麼基本的都不會?」和希望可以對瀏覽器的運作方式更加透徹,
希望趁有時間時來重新複習一下JS的事件是如何傳遞的,以及如何透過一些常見的手段來使網頁的listener不要過多來優化使用者體驗。
事件基礎知識
捕獲與冒泡
DOM的事件在傳播時,事件會以三個階段進行。
捕獲 - 事件從根節點開始往下傳遞到target的過程。
若此向下過程有經過其他綁定同類型事件之元素,該元素之事件會處在CAPTURING_PHASE
之階段。目標 - 當事件確實到達目標元素時,該目標的事件函數會處在
AT_TARGET
之階段。冒泡 - 事件從目標元素離開,一路往上從子節點逆向傳回至根節點的過程。
若此向上過程有經過其他綁定同類型事件之元素,該元素之事件會處在BUBBLING_PHASE
之階段。
綁定事件
參考了一下MDN跟Web Fundamental的文件,我們來整理一下為一個DOM物件綁定事件所要用到的參數。
element.addEventListener( |
移除事件
若要解除事件的註冊,則可透過removeEventListener()來取消。
// 須要注意cb這個函數必須是跟綁定時同一個實體(亦即是必須指向同一個位置的變數) |
const btn = document.querySelector('#btn'); |
事件傳遞之範例
試想我們有一個簡易的導航欄結構如下。
<html> |
試著把這三個DOM物件之節點都各綁上兩個點擊事件(捕獲和點擊的區段都監聽),並記錄Phase是怎麼變化的。
const navbar = document.querySelector('.navbar'); |
點一下導航欄的超連結,可得到以下結果。
navbar capturing |
取消事件傳遞
可以透過stopPropagation()
或stopImmediatePropagation()
中止事件鏈的傳遞,
加在哪裡事件就斷在哪裡,不會繼續傳遞。
stopPropagation()
btn.addEventListener('click', (e) = >{ |
結果:
我話講完?誰同意?誰反對? |
stopImmediatePropagation()
// 事件不會被傳遞至下一個node,且同層級的node之其他listener也無法觸發事件。 |
結果:
我話講完?誰同意?誰反對? |
取消預設行為
preventDefault()
瀏覽器常常會有一些預設行為,e.g.<a>
會開啟新連結,<form>
提交時會向server提交表單。
想要取消這些預設行為,可以使用preventDefault()
,須要注意的是它無法中斷事件鏈的傳遞。
form.addEventListener('submit', (e) => { |
事件代理
想像<ul>
有一百個一千個<li>
或是<svg>
有很多個資料點<circle>
的情況,
若是對它們的子代綁定event handler將會嚴重影響瀏覽器之效能破壞使用者體驗,
此時我們可以透過將event handler統一綁在它們共同的親代上來達到簡易的使用者體驗優化。
<ul class="pagination__bar"> |
// 不跳轉頁面,純顯示子代<li>之href屬性 |