JS-事件機制的原理

事件冒泡 (Event Bubbling) & 事件捕獲 (Event Capturing)

前言

在 2018 iT 邦幫忙鐵人賽,看到Kuro大的重新認識 JavaScript 系列文,仔細的閱讀後,紀錄自己觀念不足的部份,也非常推薦給大家觀看此系列文。


事件冒泡 (Event Bubbling)

圖片來源: Event Flow: capture, target, and bubbling

事件冒泡指的是「從啟動事件的元素節點開始,逐層往上傳遞」,直到整個網頁的根節點,也就是 document。


事件捕獲 (Event Capturing)

圖片來源: Event Flow: capture, target, and bubbling

剛剛說過「事件冒泡」機制是由下往上來傳遞,那麼「事件捕獲」(Event Capturing) 機制則正好相反。


事件傳遞順序

既然事件傳遞順序有兩種機制,那我怎麼知道事件是依賴哪種機制執行的?

答案是:兩種都會執行。

圖片來源: W3C, DOM event flow

開啟console,分別點擊父元素、子元素
事件機制的原理

事件監聽 EventTarget.addEventListener()

addEventListener() 基本上有三個參數,分別依序是

  • 事件名稱
  • 事件的處理器(事件觸發時執行的 function)
  • Boolean,由這個 Boolean 決定事件是以「捕獲」或「冒泡」機制執行,若不指定則預設為「冒泡」。
1
<button id="btn">Click</button>
綁定監聽動作
1
2
3
4
5
var btn = document.getElementById('btn');

btn.addEventListener('click', function(){
console.log('HI');
}, false);

使用這種方式來註冊事件的好處是可以重複指定多個「處理器」(handler) 給同一個元素的同一個事件:

1
2
3
4
5
6
7
8
9
var btn = document.getElementById('btn');

btn.addEventListener('click', function(){
console.log('HI');
}, false);

btn.addEventListener('click', function(){
console.log('HELLO');
}, false);

點擊後 console 出現:

1
2
"HI"
"HELLO"

若是要解除事件的註冊,則是透過 removeEventListener() 來取消。

removeEventListener() 的三個參數與 addEventListener() 一樣,分別是「事件名稱」、「事件的處理器」以及「捕獲」或「冒泡」的機制。

但是需要注意的是,由於 addEventListener() 可以同時針對某個事件綁定多個 handler,所以透過 removeEventListener() 解除事件的時候,第二個參數的 handler 必須要與先前在 addEventListener() 綁定的 handler 是同一個「實體(記憶體位址) 」。

二個function各自存在不同的記憶體位址
1
2
3
4
5
6
7
8
9
10
var btn = document.getElementById('btn');

btn.addEventListener('click', function(){
console.log('HI');
}, false);

// 移除事件,但是沒用
btn.removeEventListener('click', function(){
console.log('HI');
}, false);

像上面這樣,即使執行了 removeEventListener 來移除事件,但 click 時仍會出現 ‘HI’。 因為 addEventListener 與 removeEventListener 所移除的 handler 實際上是兩個不同實體(記憶體位址) 的 function 物件。

同一個function,同個記憶體位址
1
2
3
4
5
6
7
8
9
10
11
var btn = document.getElementById('btn');

// 把 event handler 拉出來
var clickHandler = function(){
console.log('HI');
};

btn.addEventListener('click', clickHandler, false);

// 移除 clickHandler, ok!
btn.removeEventListener('click', clickHandler, false);