JavaScript - DOM 事件傳遞(冒泡 & 捕獲)

當元素綁定的事件被觸發時,會經過三個階段,捕獲階段(CAPTURING_PHASE)、目標階段(AT_TARGET)與冒泡階段(BUBBLING_PHASE)。

事件傳遞

首先看看事件傳遞的表現,在 HTML 中設定一個外部元素 outer 與內部元素 inner

1
2
3
<div class="outer">
<div class="inner"></div>
</div>

將兩個元素分別綁定點擊事件。

1
2
3
4
5
6
7
8
9
const outer = document.querySelector(".outer")
const inner = document.querySelector(".inner")

outer.addEventListener("click", e => {
console.log("outer")
})
inner.addEventListener("click", e => {
console.log("inner")
})

執行的結果 :

事件傳遞的表現

可以發現,當內部元素 inner 被點擊時,在 console 中不只印出 “inner”,也印出了 “outer”,這顯示出當在觸發內部元素事件的同時,外部元素的相同事件也會一起被觸發。

傳遞階段

DOM 事件傳遞分成三個階段 :

  • 捕獲階段(CAPTURING_PHASE) : 事件從 window 向下傳遞到目標元素的過程。
  • 目標階段(AT_TARGET) : 事件傳遞到目標本身。
  • 冒泡階段(BUBBLING_PHASE) : 事件由目標向上傳遞回 window

DOM 事件傳遞示意圖

( 圖片來源 : W3C - event flow )

addEventListener() 回調函式中,可以通過事件物件 eeventPhase 屬性,確認當前事件在傳遞中的哪一個階段被觸發。

addEventListener() 的第三個參數為 useCapture,意思是 listener 是否在捕獲階段被觸發。若為 true,即決定回調函式觸發於捕獲階段,為 false 則觸發於冒泡階段。

用與上例相同的 HTML 結構,看看以下範例 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const outer = document.querySelector(".outer")
const inner = document.querySelector(".inner")

outer.addEventListener(
"click",
e => {
console.log("outer CAPTURING:", e.eventPhase)
},
true
)
outer.addEventListener(
"click",
e => {
console.log("outer BUBBLING:", e.eventPhase)
},
false
)
inner.addEventListener(
"click",
e => {
console.log("inner CAPTURING:", e.eventPhase)
},
true
)
inner.addEventListener(
"click",
e => {
console.log("inner BUBBLING:", e.eventPhase)
},
false
)

當點擊內部元素 inner 後,得到的結果為 : ( 1 : 捕獲階段,2 : 目標階段,3 : 冒泡階段。)

1
2
3
4
"outer CAPTURING:" 1
"inner CAPTURING:" 2
"inner BUBBLING:" 2
"outer BUBBLING:" 3

可以看到首先事件捕獲到外部元素 outer,接著來到目標元素 inner,最後再冒泡傳回 outer

而在過程中內部元素的 eventPhase 都是 2,也就是說當事件已經傳到目標身上進入目標階段(AT_TARGET)時,就沒有捕獲與冒泡的分別。

參考資料