事件傳遞機制-捕獲與冒泡

別人說不難,但我怎麼覺得有點難。

把不懂的地方寫下來提醒自己

我會搞混的點應該是因為沒有搞清楚這件事:一個 addEventListender 只可以監聽「一個階段」

在預設的情況下 addEventListender 監聽的是「冒泡階段」,所以你絕對不會看到外面的元素比裡面的元素還要早被觸發,因為大家都是監聽冒泡階段。也就是說外面的元素一定要等到 Target Phase 結束之後,進到冒泡階段往上傳才會一個一個被觸發。

那你要怎麼讓外面的元素先被觸發?讓它監聽捕獲階段

大概簡單來說就是這樣子吧…

事件的傳遞流程

事件的傳遞機制其實分成「冒泡」跟「捕獲」,參考這張圖:

event-flow

意思是說,當你點下 <td> 時,事件會先從 window 一路傳下來,接著到了 <td> 後,在一路傳回去。

  • window<td> 的這一段叫做「捕獲階段(Capture phase)」
  • <td>window 的這一段叫做「冒泡階段(Bubbling Phase)」
  • 至於「Target Phase」就是觸發事件的那個元素,因為它是目標,所以沒有分冒泡或捕獲

所以從上圖可以知道事件的傳遞機制是:先捕獲,在冒泡

我們可以來實作看看就知道了:

1
2
3
4
5
<div class="outter">
<div class="inner">
<button class="btn">btn</button>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 監聽捕獲階段
function addEventCapturing(selector) {
document.querySelector(selector)
.addEventListener('click', function(e) {
console.log(selector, '捕獲')
}, true)
}
// 監聽冒泡階段
function addEventBubbling(selector) {
document.querySelector(selector)
.addEventListener('click', function(e) {
console.log(selector, '冒泡')
}, false)
}
// 讓 outter 監聽捕獲階段
// 預期順序: outter -> btn -> inner
addEventBubbling('.btn')
addEventBubbling('.inner')
addEventCapturing('.outter')

刻意設成這樣的用意是為了讓你理解,只要讓外面的元素監聽「捕獲」,它就可以比裡面早被觸發。

Output:

1
2
3
.outter 捕獲
.btn 冒泡
.inner 冒泡

接著讓每個元素都同時監聽「捕獲」與「冒泡」階段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function addEventBoth(selector) {
// 設置監聽器(聽冒泡階段)
document.querySelector(selector)
.addEventListener('click', function() {
console.log(selector, '冒泡')
}, false)
// 設置監聽器(聽捕獲階段)
document.querySelector(selector)
.addEventListener('click', function() {
console.log(selector, '捕獲')
}, true)
}
// 讓每個元素同時監聽兩個階段
addEventBoth('.btn')
addEventBoth('.inner')
addEventBoth('.outter')

Output:

1
2
3
4
5
6
.outter 捕獲
.inner 捕獲
.btn 捕獲
.btn 冒泡
.inner 冒泡
.outter 冒泡

補充-在 Target Phase 中的捕獲與冒泡

[FE102] 中提到,如果在 Target Phase 同時監聽捕獲與冒泡階段,這時候會根據 addEventListener 的「設定順序」來執行,因此照這個邏輯底下應該要輸出:冒泡 -> 捕獲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function addEventBoth(selector) {
// 設置監聽器(聽冒泡階段)
document.querySelector(selector)
.addEventListener('click', function() {
console.log(selector, '冒泡')
}, false)
// 設置監聽器(聽捕獲階段)
document.querySelector(selector)
.addEventListener('click', function() {
console.log(selector, '捕獲')
}, true)

}
// 讓每個元素同時監聽兩個階段
// 預期結果: .btn 冒泡 -> .btn 捕獲
addEventBoth('.btn')

但實測發現這部分似乎有被修正過,所以一樣會遵守「先捕獲,在冒泡」的這個原則:

1
2
.btn 捕獲
.btn 冒泡
mentor-program-day37 阻止預設行為 preventDefaut
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×