JavaScript淺嚐紀錄No.8-監聽(Event Listener)DOM事件並做出反應!
上一篇文章JavaScript淺嚐紀錄No.7-了解一下DOM吧!已經簡單的介紹DOM是甚麼了,有興趣的可以先去參考。
這裡我只會去介紹addEventListener這個事件監聽器,本筆記不會提有關on-event處理器的事情!
事件監聽器(addEventListener)寫法
1 | currentTarget.addEventListener(handler,function(){…},true/false) |
事件(Event)從哪裡來的?
The Event interface represents an event which takes place in the DOM.
HTML events are “things” that happen to HTML elements.
DOM事件(DOM Event)指的是某件事情(如點擊'click'
、聚焦'focus'
)發生在了DOM的身上(如某個元素節點)。
事件傳遞(Event Flow)
簡單介紹一下甚麼是事件(Event)傳遞
事件監聽器中的第3個參數決定這個DOM物件是在Capture階段還是Bubbling階段被觸發(如果這個DOM物件本身就是觸發事件的元素,就是在At target階段的話,就一定會被觸發,而不須理會綁定的參數的是Capture還是Bubbling)。
上面圖片中所畫的紅色跟綠色箭頭就是我們說的Event Flow(事件傳遞),如何去判斷現在是在
事件傳遞的哪個階段,可以透過被監聽的DOM物件的這段語法event.eventPhase
來查詢,event.eventPhase
回傳的值會讓我們知道這個DOM物件是在哪個階段被觸發的,如果回傳1
就代表它是在Capture Phase、2
代表At target Phase、3
代表 Bubbling Phase,這3個詞組合起來就是一個完整的Event Flow(事件傳遞)。
Event Flow(事件傳遞)的3個階段分別代表什麼?
- Capture Phase:event事件從上(根元素)到下(target元素)傳遞,並且在途中去觸發有綁定相同event事件的元素(第3個參數要設定
true
),去尋找觸發event事件的元素。 - At target Phase:當event事件到達觸發event事件的元素時。
- Bubbling Phase:event事件從下(target元素)到上(根元素),往來時路傳遞回去,並且在途中去觸發有綁定相同event事件的元素(第3個參數要設定
false
)。
來看幾個範例:
Capturing
請開啟Chrome 開發者工具來檢視event.target
。
See the Pen capture phase by KUO JOUCHUN (@JOUCHUN) on CodePen.
當點擊藍色Capturing按鈕時,會發現執行的順序是從list
→listCapturing
→btnCapturing
,這是因為我們將這3個元素第3個參數都設定成true
,所以click
事件會從藍色btnCapturing
這個DOM物件最上層開始往下,順序類似body
→list
→listCapturing
→btnCapturing
,我們沒有在body
身上綁定事件監聽器,所以當事件傳遞到body
時,並不會觸發任何行為,我們有在list
及listCapturing
身上各綁定了會在Capture Phase時被觸發的click
事件(當然它們也都會在At target Phase時也會被觸發),所以當事件傳遞到list
→listCapturing
時,會依序觸發綁定的事件。
Bubbling
請開啟Chrome 開發者工具來檢視event.target
。
See the Pen bubbling phase by KUO JOUCHUN (@JOUCHUN) on CodePen.
當點擊紅色Bubbling按鈕時,會發現執行的順序是跟上面的Capturing按鈕是相反的,是`btnBubbling`→`listBubbling`→`list`,這是因為我們將這3個元素第3個參數都設定成`false`,所以`click`事件會從紅色`btnBubbling`這個DOM物件開始往上,順序類似`btnBubbling`→`listBubbling`→`list`→`body`,同樣的我們並沒有在`body`身上綁定事件監聽器,所以當事件傳遞到`body`時,並不會觸發任何行為,我們有在`listBubbling`及`list`身上各綁定了會在Bubbling Phase時被觸發的`click`事件(當然它們也都會在At target Phase時也會被觸發),所以當事件傳遞到`listBubbling`→`list`時,會依序觸發綁定的事件。到這裡大家對事件傳遞應該都有一點概念了,但這時我卻產生了一個疑問,如果我是這麼綁定事件的話,會發生甚麼事?
我在list
第3個參數設定false
,而btnCapturing
跟listCapturing
設定為true
,再來看看會是甚麼結果。
1 | let list = document.querySelector('.list') |
這時候會發現事件傳遞的方向其實一樣是body
→list
→listCapturing
→btnCapturing
→btnCapturing
→listCapturing
→list
→body
,同樣的因為body
並沒有綁定任何監聽器所以會被略過,而在事件捕獲階段,到達list
時,發現list
並沒有綁定在當它處於捕獲階段時的事件監聽器,所以會略過它再到listCapturing
→btnCapturing
去執行事件函式,但這時候的事件傳遞並不會因為已經到了btnCapturing
就停止,而是會繼續執行冒泡階段,所以會繼續從btnCapturing
開始再繼續往上傳遞事件,但btnCapturing
及listCapturing
並沒有綁定會在冒泡階段執行的是監聽器,所以被略過,事件被傳遞到了list
時,就發現原來你有綁定處於冒泡階段時的事件監聽器喔!那就來執行一下你的事件函式。
到這我的結論就是:如果有一個DOM物件綁定事件監聽器的話,那麼這個事件一定會從上到下,再從下到上傳遞,至於這個event事件經過其他DOM物件時,會不會被觸發取決於事件傳遞到該DOM物件時,它有沒有綁定監聽器;甚麼時候被觸發,取決於綁定的第3個參數是
true
(Capture階段)還是false
(Bubbling階段),如果是true
的話,那麼在事件傳遞是Capture階段經過該DOM物件的話,就會被觸發該事件函式,反之如果是false
的話,就會在事件傳遞是Bubbling階段時被觸發。
大家可以看看這部影片techsith-Event Bubbling and Capturing in JavaScript。
停止預設行為及停止事件傳遞
event.preventDefault()
甚麼是預設行為?在HTML中的<a href="#"></a>
的預設行為就是連結別的網頁,雖然我們會透過設定href="#"
來取消跳轉調別的網頁,但認真看一下上面的網址其實是會多出一個#,所以不管怎麼設定其實網頁還是會跳轉。而<input type="submit">
的預設行為就是將表單的資料提交到伺服器,也因此按下按鈕時會導致頁面刷新。
但我們在設計網頁時,常常是需要透過這些它們來再目前頁面做一些效果,例如按下按鈕就會出現一個小方塊,但這些預設行為會導致頁面刷新而無法實現這些效果,因此可以透過event.preventDefault()
來取消這些預設行為,但是事件傳遞並不會被取消喔。1
2
3listCapturing.addEventListener('click', function (event) {
event.preventDefault();
})event.stopPropagation()
上面已經有介紹過捕獲或冒泡的事件傳遞,而event.stopPropagation()
就是來停止這些傳遞行為的,也因此當在點擊btnCapturing或是brnBubbling時,就不會再將事件從上到下、從下到上傳遞,而是只有是在At_Target階段才能被觸發事件1
2
3listCapturing.addEventListener('click', function (event) {
event.stopPropagation();
})
註:event
是事件發生時,一定會出現的物件,透過這個event
物件可以去尋找事件屬性(event.target
可以知道目前點擊的DOM物件)或是使用物件內的方法(event.stopPropagation()
停止傳遞行為的方法),通常會用event
或e
來表示event
物件。
事件(Event)種類
事件種類太多了,如果想要了解可以參考MDN-Event reference,比較常用的就是滑鼠的click
事件、表單的submit
、change
(表單內容改變並且不在focus
的狀態)、input
(表單內容改變)事件。
事件委派(Event Delegation)
當需要綁定相同事件在多個不同元素,且這些元素擁有共同的祖宗元素時,就可以借助事件具有傳遞的特性(主要是指Bubbling Event),將該事件綁定在該祖宗元素上,而無須在子元素上一一綁定事件,而這樣的行為就是**事件委派(Event Delegation)**。
為什麼不建議將事件一個一個綁定呢?
這是因為設定一個函式,就會占用一份記憶體空間,所以使用事件委派的方式,可以減少記憶體空間的使用。
另外在不需要事件監聽的狀況下,可以統一移除,也不需要一個一個去移除事件監聽器了。
當我們使用事件委派(Event Delegation)的方式來綁定監聽器,除了借助事件傳遞的特性之外,還借助了事件擁有的屬性event.target
,event.target
可以幫助我們知道現在觸發事件的目標元素是哪個,也因此我們可以透過操作這個目標元素來達成我們的目的。
請開啟Chrome 開發者工具來檢視event.target
。
See the Pen Event Delegation by KUO JOUCHUN (@JOUCHUN) on CodePen.
參考:
JavaScript 事件對記憶體和效能的影響
Event Delegation — 事件委派介紹 與 觸發委派的回呼函數
JavaScript 面試:事件傳遞機制和事件委託 Event Propagation & Event Delegation - 彭彭直播