PJCHENder 未整理筆記

[JS] Event Capturing and Bubbling

2017-10-03

[JS] Event Capturing and Bubbling

@(JavaScript)

tags: event, propagation, capturing, bubbling

本篇非原創,內容整理自 :thumbsup: Bubbling and Capturing @ JavaScript.info

冒泡事件(Bubbling)

冒泡(Bubbling):當一個元素被觸發時,在它身上事件處理器(event handler)會先執行,接著是它的父層元素,然後是所有其它的上層元素也都會被觸發。

舉例來說,當 <p> 被點擊時,<p> 的事件處理器會先被觸發,接著依序是 <div> 然後是 <form>

1
2
3
4
5
6
7
8
<!--
Event Bubbling: <p> -> <div> -> <form>
-->
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>

幾乎所有的事件都是冒泡事件,除了少數事件不是,如 focus 等。

因此最上層的元素幾乎可以知道內層元素所有發生的事情,其中包含幾個重要的屬性:

  • event.target:觸發此事件的元素可以透過 event.target 來取得,這個元素在整個冒泡過程中不會改變。
  • event.currentTarget:綁定此事件的元素,通常和 this 指的是同一個元素。
  • this:指的是處理此事件的元素,和 event.currentTarget 指稱同一個元素。

停止冒泡

冒泡事件會從被觸發該事件的 target element 一直上外傳遞,基本上會一直到 <html> 然後到 document 物件,有些事件甚至會到 window

  • event.stopPropagation():在冒泡的過程中,任何事件處理器都能夠決定要不要終止繼續向外冒泡,只需使用 event.stopPropagation() 即可。
  • event.stopImmediatePropagation():如果一個元素被綁了一個以上的事件處理器時,此時其中一個事件雖然使用 event.stopPropagation() 來停止向外冒泡,但這個元素上的其他的事件處理器仍然會執行,此時可以使用 event.stopImmediatePropagation() 來停止這個元素繼續冒泡,並避免這個元素上的其他事件處理器被執行

stopPropagation() 會讓相同元素的其他 handlers 被執行,但是 stopImmediatePropagation() 則會避免相同元素的其他事件再次被觸發。

要留意的是,除非有需要,否則不需要停止冒泡事件,舉例來說我們有一個 <ul> 選單,但如果在其中的 <li> 上使用了 event.stopPropagation() 的話,會使得 <ul> 沒辦法監聽到 <li> 的事件。

:exclamation: 除非有需要,否則不需要停止冒泡事件。

捕獲事件(Capturing)

在事件處理的過程中還有一個稱作「捕獲(capturing)」的過程,但它實際上很少被用到

在標準的 DOM 事件中,event propagation 可以分成三個階段:

  • Capturing Phase:事件由外而內傳遞到被觸發事件的元素。
  • Target Phase:事件抵達被觸發事件的元素。
  • Bubbling Phase:事件從該元素透過冒泡從內而外傳遞。

舉例來說,當我們點擊 DOM 裡面的 <td> 時:

  • Capturing Phase(紅色線):該事件會透過捕獲由外層傳進內層。
  • Target Phase(藍色區塊):接著事件到達觸發此事件的元素。
  • Bubbling Phase(綠色線):此事件透過冒泡又內向外傳遞。

當使用 addEventListener(event, handler) 的時候,預設只會處理到 event propagation 中的第二階段(target phase)和第三階段(bubbling phase),如果想要使用到第一階段(capturing phase),那麼就必須在 addEventListener() 中代入第三個參數為 true(預設是 false):

1
elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);

當我們把 addEventListener() 中的最後一個參數設為 true 時,整個流程會變這樣:

1
2
3
4
5
6
7
8
9
10
<!--
Event Capturing: <html> -> <body> -> <form> -> <div> -> <p>
Target Phase: <p>
Event Bubbling: <p> -> <div> -> <form> -> <body> -> <html>
-->
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>

檢視事件的傳遞階段:event.eventPhase

另外也可以使用 event.eventPhase 來取得此事件目前是在 event propagation 中的哪一個階段:

  • 1 : 表示 CAPTURING_PHASE
  • 2 : 表示 AT_TARGET
  • 3 : 表示 BUBBLING_PHASE

範例程式碼

See the Pen [JS] Event Bubbling and Capturing by PJCHEN (@PJCHENder) on CodePen.

參考資料

掃描二維條碼,分享此文章