本文同步發布於個人部落格
這個概念其實沒那麼難~
本質其實就是使用者互動到 DOM 元素提交回應的一個雙向過程而已。
他雖然是 JS 裡一個很基礎、必須知道的知識,但實務上它可能是個小透明,因為現在的框架都會幫忙處理好這些事情。
但面試偶爾還是會考一下這個概念,所以還是得把它記起來。
先說一下,HTML 的格式基本就是一個元素嵌著一個個元素,這樣層層相扣的結構。
了解事件捕獲與事件冒泡時可以把 DOM tree 想像成很多房間,每個房間裡面都還有其他房間,也許會好懂一些。
<div class="room1">
<div class="room2">
<div class="room3">
<div class="room4">
<button> Click! </button>
</div>
</div>
</div>
</div>
所以看著上面的結構我們開始來想像,什麼是事件捕獲 (Event Capturing)。
因為 room1 是最外層的房間,所以當使用者點擊了 button 時,雖然看似點擊了 room4 裡的 button,但實際上這個事件會從最外層的 room1 開始捕獲,然後一路往裡面傳遞到 room4 的 button。
那是一個一瞬間的事,想像使用者提交一個互動事件給 room1 的守衛時,他非常迅速地把這個事件傳遞給了 room2 的守衛,然後一路傳下去到了 button。
那 button 接收到事件後總得回應吧? (BTW,接受到事件的當下其實稱為目標階段)
所以 button 會開始丟回他接收到事件的信號,一路從 button 傳遞回 room1。
在傳遞這個回程訊號期間,如果經過的 DOM 元素有 eventHandler,就會被觸發執行,比如:
<div class="room1">
<div class="room2"
onclick="console.log('room2 clicked')"
>
<div class="room3">
<div class="room4">
<button
onclick="console.log('button clicked')"
>
Click!
</button>
</div>
</div>
</div>
</div>
這裡要特別說明一點,原生 JS 預設在冒泡事件階段會觸發事件處理器,但在捕獲階段不會是因為 addEventListener
的第三個參數 options
內的 capture
預設是 false
,代表冒泡階段才觸發事件處理器。
反之設為 true
,則會在捕獲階段觸發事件處理器。
當然也可以掛兩個事件監聽器 (addEventListener
),一個設為捕獲階段,一個設為冒泡階段,這也是沒什麼問題的。
這樣在目標階段時,會先觸發捕獲階段的事件處理器,然後再觸發冒泡階段的事件處理器。
不過實務上已經很少直接使用 addEventListener
了。
在這個 Vue 跟 React 充斥的前端世界,這些東西都被他們包好好的。
所以只要記得:
事件捕獲與冒泡其實本質就是一個雙向溝通的過程
舉個跟標題符合的例子 XD
小 A (使用者) 暗戀小 B (button) 很久了,終於鼓起勇氣寫了一封情書。
但他們之間隔著好幾位同學,於是小 A 將信悄悄遞給身邊的同學,同學再傳給下一位同學,這樣一手一手地傳遞下去,直到信終於送到小 B 手上 (事件捕獲)。
小 B 拿到信的那一刻,心花怒放,立刻喊出:「我收到情書了!」(目標階段的事件處理器觸發)。
接著她馬上寫下「我也喜歡你 ❤️」的回信,沿著同樣的路線,一手一手地從小 B 傳回小 A (事件冒泡)。
在這段回程中,途中經過的某位同學 C 剛好對信的內容很感興趣,忍不住舉手告訴老師:「小 B 收到情書了!」(冒泡階段某 DOM 元素事件處理器觸發)。
所以事件捕獲與冒泡其實是一個青春戀愛物語。