事件機制
JavaScript 是「事件驅動」的程式語言,雖然瀏覽器在載入網頁後就會讀取 JavaScript 相關程式碼,但只有等到「事件」發生 (像是使用者滑鼠點擊、按下鍵盤)才會執行相關程式。
以網頁對話框為例:按下開啟 (觸發事件)-> 彈出對話框 (執行相關事件)
這整個對話框從觸發到開啟的過程牽涉到 DOM 的事件流程,就是「網頁元素接收事件的順序」
事件流程 (event flow)
由於 inner 也是 outer 的一部分,所以點擊 inner 的時候,也點到 outer(實際上也點擊整個網頁)。假如我在兩個元素上面都加了 eventListener,知道哪個先執行就很重要。
1 | <div id="outer"> |
事件的三個階段
事件流程可以分成三個階段
1 | CAPTURING_PHASE = 1; |
- 事件冒泡 (event bubbling):
事件冒泡是從「啟動事件的元素節點」開始,逐層往上傳遞」直到網頁的根節點,也就是 document。
1 |
|
在事件冒泡的觸發順序會是:
- click
- body
- html
- document
- 事件捕獲 (event capturing)
事件捕獲的機制正好相反,觸發是順序會從根節點開始
在事件冒泡的觸發順序會是:
- document
- html
- body
- click
- AT_TARGET
DOM 事件會從根節點往下傳遞到 target,target 就是點擊的目標,如果在 target 上加上事件監聽,他就會處在 AT_TARGET 這一個 Phase。
先捕獲再冒泡
事件究竟依賴哪種機制執行?兩種都會。在點擊 td 的時候,會從 window 開始往下傳,一直傳到 td 為止,到這邊就叫做 CAPTURING_PHASE,捕獲階段。接著事件傳遞到 td 本身,這時候叫做 AT_TARGET。最後事件會從 td 一路傳回去 window,這時候叫做 BUBBLING_PHASE,冒泡階段
如何決定要在捕獲還是冒泡階段監聽這個事件? 這裡可利用 addEventListener
的第三個參數,true 代表把這個 listener 添加到捕獲階段,false 或是沒有傳就代表把這個 listener 添加到冒泡階段。
1 | target.addEventListener(type, listener, useCapture); |
以實際例子看:
1 | <div> |
幫每一個元素的每一個階段都添加事件,並且利用事件物件(event object) 提供的資訊了解事件傳遞機制。
1 | var parent = document.querySelector("#parent"); |
當我點擊子元素的時候,事件觸發在 console 輸出的結果如下:
1 | parent capturing 1 |
點擊父元素的時候,則出現
1 | parent capturing 2 |
1 是 CAPTURING_PHASE,2 是 AT_TARGET,3 是 BUBBLING_PHASE。
很明顯看出,事件的確是從最上層一直傳遞到 target。當事件傳遞到點擊的真正對象,也就是 e.target 的時候,無論你使用 addEventListener 的第三個參數是 true 還是 false,這邊的 e.eventPhase 都會變成 AT_TARGET。
所以即便調換子層的 capturing 或 bubbling 順序,也沒有什麼捕獲跟冒泡之分,所以執行順序就會根據你 addEventListener 的順序而定,先添加的先執行,後添加的後執行。
1 | child.addEventListener( |
得到
1 | parent capturing 1 |
事件的傳遞順序,只要記住兩個原則就好:
- 先捕獲,再冒泡
- 當事件傳到 target 本身,沒有分捕獲跟冒泡