1. 程式人生 > >.passive 修飾符,addEventListener()引數具體意義

.passive 修飾符,addEventListener()引數具體意義

很久以前,addEventListener() 的引數約定是這樣的:

addEventListener(type, listener, useCapture)

後來,最後一個引數,也就是控制監聽器是在捕獲階段執行還是在冒泡階段執行的 useCapture 引數,變成了可選引數(傳 true 的情況太少了),成了:

addEventListener(type, listener[, useCapture ])

去年年底,DOM 規範做了修訂:addEventListener() 的第三個引數可以是個物件值了,也就是說第三個引數現在可以是兩種型別的值了:

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

這個修訂是為了擴充套件新的選項,從而自定義更多的行為,目前規範中 options 物件可用的屬性有三個:

addEventListener(type, listener, {
    capture: false,
    passive: false,
    once: false
})

三個屬性都是布林型別的開關,預設值都為 false。其中 capture 屬性等價於以前的 useCapture 引數;once 屬性就是表明該監聽器是一次性的,執行一次後就被自動 removeEventListener 掉。

很多移動端的頁面都會監聽 touchstart 等 touch 事件,像這樣:

document.addEventListener("touchstart", function(e){
    ... // 瀏覽器不知道這裡會不會有 e.preventDefault()
})

由於 touchstart 事件物件的 cancelable 屬性為 true,也就是說它的預設行為可以被監聽器通過 preventDefault() 方法阻止,那它的預設行為是什麼呢,通常來說就是滾動當前頁面(還可能是縮放頁面),如果它的預設行為被阻止了,頁面就必須靜止不動。但瀏覽器無法預先知道一個監聽器會不會呼叫 preventDefault(),它能做的只有等監聽器執行完後再去執行預設行為,而監聽器執行是要耗時的,有些甚至耗時很明顯,這樣就會導致頁面卡頓。視訊裡也說了,即便監聽器是個空函式,也會產生一定的卡頓,畢竟空函式的執行也會耗時。

視訊裡還說了,有 80% 的滾動事件監聽器是不會阻止預設行為的,也就是說大部分情況下,瀏覽器是白等了。所以,passive 監聽器誕生了,passive 的意思是“順從的”,表示它不會對事件的預設行為說 no,瀏覽器知道了一個監聽器是 passive 的,它就可以在兩個執行緒裡同時執行監聽器中的 JavaScript 程式碼和瀏覽器的預設行為了。

下面是在 Chrome for Android 上滾動 cnn.com 頁面的對比視訊,右邊在註冊 touchstart 事件時添加了 {passive: true} 選項,左邊沒有,可以看到,右邊的順暢多了。

假如在一個 passive 的監聽器裡執行了 preventDefault() 會怎麼樣?

假如有人不小心在 passive 的監聽器裡呼叫了 preventDefault(),也無妨,因為 preventDefault() 不會產生任何效果。

let event = new Event("foo", {  // 建立一個 type 為 foo 的事件物件,可以被阻止預設行為 
  "cancelable": true
})

document.addEventListener("foo", function(event) { // 在 document 上繫結 foo 事件的監聽函式
  console.log(event.defaultPrevented) // false
  event.preventDefault()
  console.log(event.defaultPrevented) // 還是 false,preventDefault() 無效
}, {
  passive: true
})
 
document.dispatchEvent(event) // 派發自定義事件

同時,瀏覽器的開發者工具也會發出警告:

Chrome 下:

Firefox 下:

passive 監聽器能保證的只有一點,那就是呼叫 preventDefault() 無效