iT邦幫忙

2022 iThome 鐵人賽

DAY 25
0
Modern Web

前端蛇行撞牆記系列 第 25

Day25 前端蛇行撞牆記 - addEventListener() 的參數(下)

  • 分享至 

  • xImage
  •  

前言

前天的文章已經有介紹前兩個參數了,今天要來介紹第三個參數 option or useCapture

addEventListener(type, listener)
addEventListener(type, listener, options)
addEventListener(type, listener, useCapture)

第三個參數可寫可不寫,這就是之前提到比 onclick 的方式更能控制 listener 的部分。

預設都會是 useCapture: false

useCapture

接受布林值。
預設是false,不要捕獲時執行listener的意思。

這裡來說說事件傳遞機制:

事件傳遞機制:capturing, bubbling

有分為capturing, bubbling,捕獲與冒泡,請看最經典的圖:


出自:w3c

假設現在我們把<td>當成eventTarget,給他一個click事件,就會像這樣:

const td = document.querySelector('td')

td.addEventListener('click', function() {
    td.style.color = 'red';
})

按下 td 的那一刻發生了事件的傳遞,首先最外層的 window 會開始往下找被叫事件的target也就是 td 在哪裡,就會一層一層地往下找,直到找到為止。
這個階段就叫做 Capture phase(捕獲階段)

接著找到了 td 之後,就到了 Target phase(目標階段)

然後就從 td 往上冒泡,進入了 Bubble phase(冒泡階段),就是在這一時候執行 listener 的內容,之後就一直往上冒泡直到最外層的 window 才算事件傳遞結束。

這就是整個事件傳遞的過程。

那麼前面說到預設都會是useCapture: false,這個意思就是不在捕獲的時候執行listener function,而是在 bubbling 的時候執行。

MDN:

useCapture:
A boolean value indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.

Events that are bubbling upward through the tree will not trigger a listener designated to use capture. Event bubbling and capturing are two ways of propagating events that occur in an element that is nested within another element, when both elements have registered a handle for that event.

The event propagation mode determines the order in which elements receive the event. See DOM Level 3 Events and JavaScript Event order for a detailed explanation. If not specified, useCapture defaults to false.

(不負責翻譯蒟蒻)

布林值會指出在DOM tree要找到eventTarget之前,這個類型的事件在什麼時候將被派出註冊的監聽事件。
要用冒泡的事件出去DOM tree就不會在捕獲的時候觸發listener,事件冒泡與捕獲是兩個方式傳遞事件,這個發生在元素在其他元素的下層的時候,這兩個元素將都會被註冊這個監聽事件。
事件傳遞模式決定什麼時候元素要接收事件,看 DOM Level 3 Events 跟 JavaScript Event Order 有更清楚的解釋。如果沒有特別指定,useCapture的預設會是false。

options(optional)

這個物件指出listener的特徵,可以加入的屬性有:

  • capture
  • once
  • passive
  • signal

capture 捕獲

接受布林值,預設是false。
useCapture 完全一樣,也是決定要在捕獲還是冒泡去執行listener。

once 一次

接受布林值,預設是false。
會指出這個listener在被加入之後最多只會被執行一次,如果是true,執行一次之後會自動移除listener。

如果確定只要執行一次listener就可以使用,就不用用到removeEventListener()拉~

passive 被動的

接受布林值,預設為false。
如果是true,這個 listener 將永遠不會調用preventDefault(),如果一個有passive: true的 listenrer 呼叫了preventDefault(),就會產生一個警告,並且不做任何事情。

除了在 Safari 跟 IE(現在已不存在) 瀏覽器之外,瀏覽器為了一些 scroll, touch 事件,自動預設會是true。

所以 scroll, touch 事件是不能被取消預設行為的。

來試試看監聽scroll事件,又是一個 passive listener,然後在裡面preventDefault()

const container = document.querySelector(".container");

window.addEventListener("scroll", scrollHanlder, { passive: true } 
);

function scrollHanlder(e) {
  e.preventDefault();
  container.style.backgroundColor = "blue";
}

嘩~大報錯

Unable to preventDefault inside passive event listener invocation.

(翻譯蒟蒻)不能在 passive listener 的時候 preventDefault 拉!

不過前面說過本來 scroll 本來就是 { passive: true }

所以{ passive: true } 像是提早告知瀏覽器這個 listener 要不要管 e.preventDefault()的行為,讓瀏覽器在執行前就可以知道:喔!這是個 passive listener ,就直接執行listener,不要管裡面是否有e.preventDefault(),如此一來效能將會快許多。

這篇 Making touch scrolling fast by default 說得更清楚:

If you call preventDefault() in the touchstart or first touchmove events then you will prevent scrolling.

The problem is that most often listeners will not call preventDefault(), but the browser needs to wait for the event to finish to be sure of that.

Developer-defined "passive event listeners" solve this. When you add a touch event with a {passive: true} object as the third parameter in your event handler then you are telling the browser that the touchstart listener will not call preventDefault() and the browser can safely perform the scroll without blocking on the listener.
(翻譯蒟蒻)
如果你呼叫 preventDefault()touchstarts, touchmove 的話就會預防滑動。
但問題是常常 listener 不會呼叫preventDefault(),但瀏覽器需要等待事件結束才能確定這件事。
開發者定義 “passive event listeners” 解決了這件事。當你加了 touch事件第三個參數放了{passive: true},那你就是在告訴瀏覽器這個 touch 事件將不會呼叫 preventDefault() ,這樣瀏覽器就不會阻塞listener,然後安全地履行滑動。

signal

一個AbortSignal

listener將會被移除當使用了 AbortSignal 物件的 abort 屬性,
是當 DOM 請求正在與之溝通的信號被中止時調用。

如果沒有被指定,就沒有AbortSignal會跟listener有關聯。

這個屬性還不太熟悉,可能日後有在跟瀏覽器fetch才會理解。

結論

  • 預設的 addEventListener() 都是在冒泡才執行listener
  • option要用物件的方式去放置,裏面的passive, once, capture, 都是屬性,而這些的預設都是 false。 signal 則不一樣是一個AbortSignal
  • { passive: true }是為了提前告知瀏覽器這個listener不會preventDefault()

參考資料:
MDN EventTarget.addEventListener()
MDN EventTarget.removeEventListener()
Passive event listeners
addEventListener 的第三個參數
移动Web滚动性能优化: Passive event listeners
[note] Event Capturing and Bubbling


上一篇
Day24 前端蛇行撞牆記 - 製作簡易的檔案拖拉上傳 / drag and drop
下一篇
Day26 前端蛇行撞牆記 - 在 Vue 3 偵測螢幕尺寸變化 / VueUse及原生JS
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言