iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
2
Modern Web

前端三十 - 成為更好的前端工程師系列 第 7

07. [JS] 瀏覽器 DOM 元素的事件代理是指什麼?

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20190923/20111380YnENutxjX3.jpg

本系列來到了 JavaScript 相關的主題,預計會在這逐步前進好一陣子,讓我們從較簡單的主題開始吧;今天就先來聊聊瀏覽器中的事件代理是怎麼一回事。

本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!

事件

在網頁中,如果想與使用者進行「互動」,勢必需要透過一些方法捕捉使用者做了什麼。當然,瀏覽器開發者們早已依據 W3C 的事件規範,幫我們實作好了底層的邏輯,我們只需要透過 Web API 中的 DOM Event,針對想監聽的 DOM 元素 & 事件,註冊事件監聽器(Event Listener)便可以輕鬆掌握使用者在網頁上的一舉一動。

事件監聽

前面剛提到了「事件監聽器」,開發者可以在想要監聽事件的 DOM 元素上,透過 addEventListener 註冊監聽器。例如:

document.querySelector('#id').addEventListener('click', clickHandler)

當使用者對 #id 元素點擊時,便會觸發 clickHandler,並會傳入一個事件物件,內容包含事件傳遞過程中的紀錄,例如目標元素、當前元素、傳遞階段等等。開發者便可以從中取得所需要的資料,並對資料做你想做的事情。

一個現代網站有大量的使用者互動是稀鬆平常的事情,若是透過事件監聽一個一個寫,那豈不是到處都要註冊事件監聽?除了效能很差,寫起來也很麻煩;這也就是「事件代理」的重要性了!

不過在聊到事件代理之前,我們需要先理解 DOM Tree 上的事件傳遞機制是如何運作的。

事件傳遞

可以參考這張 W3C 所定義的 Event Flow 圖:

event flow

規範中定義了事件傳遞的三個階段:

  • 捕獲階段:由 DOM Tree 的根節點依序向內傳遞,過程中觸發各別元素的捕獲階段事件監聽。
  • 目標階段:到達事件目標(Event Target),依照註冊順序觸發事件監聽
  • 冒泡階段:由事件目標依序向外傳遞,過程中觸發各別元素的冒泡階段事件監聽。

如圖所示,當使用者觸發一個 DOM 元素的事件時,首先會進入 「捕獲階段(Capture Phase)」,由根結點逐步向事件目標傳遞;到達目標後則進入「目標階段(Target Phase)」,接著就開始折返,進入向根結點傳遞的「冒泡階段(Bubbling Phase)」。

在開發者使用 addEventListener 註冊事件監聽器時,可以透過傳遞第三個參數,指定此事件監聽要在什麼階段觸發:

elem.addEventListener('click', eventHandler) // 未指定,預設為冒泡
elem.addEventListener('click', eventHandler, false) // 冒泡
elem.addEventListener('click', eventHandler, true) // 捕獲
elem.addEventListener('click', eventHandler, {
  capture: true // 是否為捕獲。IE、Edge 不支援。 其他物件屬性請參考 MDN
})

ie2

透過簡單的來回傳遞,這樣開發者就能更精準的控制觸發的時機了!

事件代理

好啦,我們終於聊到了事件代理。由於事件傳遞的機制,子元素的事件在傳遞過程中勢必會經過他的父元素;而事件代理,顧名思義就是將子元素事件監聽器交由父元素代理。

什麼意思呢?我們直接看個簡單的對照範例:

差異就只在事件監聽的目標元素。

無事件代理的版本中,事件監聽器註冊在每一個 li 上,當數量越來越多,瀏覽器也就建立了越來越多的監聽器,無形中對效能有很大的傷害;反之,在有事件代理的版本則將事件監聽器註冊在外層的 ul 上,無論內容有多少,瀏覽器都只需要負擔一組事件監聽器的消耗。

延伸閱讀

現在軟體開發中,使用套件是稀鬆平常的;在 DOM 事件處理的這部分,jQuery & Vue 都將原生的事件監聽器做了包裝,方便開發者快速設定、取用,甚至會自動幫你移除無用的事件監聽。

但在 React 中,React DOM 上直接註冊的事件監聽器,其實監聽的是 React 額外封裝過的 React DOM Event,並將全部事件代理到 document 上,與原生事件有蠻大的不同;特別是在執行順序上,若有混用 React DOM Even tListener 及原生的 addEventListener,事件監聽器之間的執行順序很有可能會不如預期,寫 React 的朋友要特別注意喔!

有興趣深入研究的朋友可以從 這邊 開始查找 React 關於事件處理的 Code。

結語

前端工程師在製作網站時,活用大量的互動式設計讓網站更吸引人的同時,也要記得多少活用事件代理的概念,更優雅、乾淨、高效的處理互動事件吧!另外,筆者先前也寫過一篇討論 DOM 事件傳遞的相關文章,並遇到了一個蠻奇妙的 Bug,有興趣的朋友歡迎移駕 我的部落格

以上就是今天的分享,若讀者您對文中內容有任何想法,都歡迎你於底下討論區回應;或是參考 本系列文其他文章,讓我們一起繼續這趟每天逐步變強的旅程!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
06. [JS] 請你在旁邊的白板寫個快速排序演算法。
下一篇
08. [JS] 請寫出間隔一秒印出 1, 2, 3, 4, 5 的程式碼。
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
huli
iT邦新手 3 級 ‧ 2019-09-23 23:12:28

最後那個 bug 稍微試了一下覺得滿有趣的,有在 Medium 原文回一些想法囉~

看更多先前的回應...收起先前的回應...
Yenting iT邦新手 5 級 ‧ 2019-09-23 23:20:39 檢舉

文章讚讚,但想給第二張圖一個噓(by被嚇到的讀者)

Yenting iT邦新手 5 級 ‧ 2019-09-23 23:21:40 檢舉

發現不小心把留言留成回覆了哈哈哈哈哈
順便跟huli say hi~

Gary iT邦新手 5 級 ‧ 2019-09-23 23:31:31 檢舉

感謝 huli 大大回應,終於得到解答了~
/images/emoticon/emoticon02.gif

Gary iT邦新手 5 級 ‧ 2019-09-23 23:32:35 檢舉

Yenting 第二章圖是 event flow 還是 IE?
我很抱歉 XD

huli iT邦新手 3 級 ‧ 2019-09-24 00:29:38 檢舉

Yenting 安安
大家安安

我要留言

立即登入留言