防抖和節流嚴格算起來應該屬於性能優化的知識,但實際上遇到的頻率至關高,處理不當或者聽任無論就容易引發瀏覽器卡死。因此仍是頗有必要早點掌握的。(信我,你看完確定就懂了)算法
先說一個常見的功能,不少網站會提供這麼一個按鈕:用於返回頂部。
chrome
這個按鈕只會在滾動到距離頂部必定位置以後纔出現,那麼咱們如今抽象出這個功能需求-- 監聽瀏覽器滾動事件,返回當前滾條與頂部的距離
這個需求很簡單,直接寫:瀏覽器
function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滾動條位置:' + scrollTop); } window.onscroll = showTop
可是!性能優化
在運行的時候會發現存在一個問題:這個函數的默認執行頻率,太!高!了!。 高到什麼程度呢?以chrome爲例,咱們能夠點擊選中一個頁面的滾動條,而後點擊一次鍵盤的【向下方向鍵】,會發現函數執行了8-9次!
閉包
然而實際上咱們並不須要如此高頻的反饋,畢竟瀏覽器的性能是有限的,不該該浪費在這裏,因此接着討論如何優化這種場景。app
基於上述場景,首先提出第一種思路:在第一次觸發事件時,不當即執行函數,而是給出一個期限值好比200ms,而後:dom
效果:若是短期內大量觸發同一事件,只會執行一次函數。函數
實現:既然前面都提到了計時,那實現的關鍵就在於setTimeOut
這個函數,因爲還須要一個變量來保存計時,考慮維護全局純淨,能夠藉助閉包來實現:性能
/* * fn [function] 須要防抖的函數 * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //藉助閉包 return function() { if(timer){ clearTimeout(timer) //進入該分支語句,說明當前正在一個計時過程當中,而且又觸發了相同事件。因此要取消當前的計時,從新開始計時 timer = setTimeOut(fn,delay) }else{ timer = setTimeOut(fn,delay) // 進入該分支說明當前並無在計時,那麼就開始一個計時 } } }
固然 上述代碼是爲了貼合思路,方便理解(這麼貼心不給個贊咩?),寫完會發現其實 time = setTimeOut(fn,delay)
是必定會執行的,因此能夠稍微簡化下:優化
/*****************************簡化後的分割線 ******************************/ function debounce(fn,delay){ let timer = null //藉助閉包 return function() { if(timer){ clearTimeout(timer) } timer = setTimeout(fn,delay) // 簡化寫法 } } // 而後是舊代碼 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滾動條位置:' + scrollTop); } window.onscroll = debounce(showTop,1000) // 爲了方便觀察效果咱們取個大點的間斷值,實際使用根據須要來配置
此時會發現,必須在中止滾動1秒之後,纔會打印出滾動條位置。
到這裏,已經把防抖實現了,如今給出定義:
繼續思考,使用上面的防抖方案來處理問題的結果是:
可是若是產品同窗的指望處理方案是:即便用戶不斷拖動滾動條,也能在某個時間間隔以後給出反饋呢?(此處暫且不論哪一種方案更合適,既然產品爸爸說話了咱們就先考慮怎麼實現)
其實很簡單:咱們能夠設計一種相似控制閥門同樣按期開放的函數,也就是讓函數執行一次後,在某個時間段內暫時失效,過了這段時間後再從新激活(相似於技能冷卻時間)。
效果:若是短期內大量觸發同一事件,那麼在函數執行一次以後,該函數在指定的時間期限內再也不工做,直至過了這段時間才從新生效。
實現 這裏藉助setTimeout
來作一個簡單的實現,加上一個狀態位valid
來表示當前函數是否處於工做狀態:
function throttle(fn,delay){ let valid = true return function() { if(!valid){ //休息時間 暫不接客 return false } // 工做時間,執行函數而且在間隔期內把狀態位設爲無效 valid = false setTimeout(() => { fn() valid = true; }, delay) } } /* 請注意,節流函數並不止上面這種實現方案, 例如能夠徹底不借助setTimeout,能夠把狀態位換成時間戳,而後利用時間戳差值是否大於指定間隔時間來作斷定。 也能夠直接將setTimeout的返回的標記當作判斷條件-判斷當前定時器是否存在,若是存在表示還在冷卻,而且在執行fn以後消除定時器表示激活,原理都同樣 */ // 如下照舊 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滾動條位置:' + scrollTop); } window.onscroll = throttle(showTop,1000)
運行以上代碼的結果是:
講完了這兩個技巧,下面介紹一下平時開發中常遇到的場景:
上述內容基於防抖和節流的核心思路設計了簡單的實現算法,可是不表明實際的庫(例如undercore js)的源碼就直接是這樣的,最起碼的能夠看出,在上述代碼實現中,由於showTop
自己的很簡單,無需考慮做用域和參數傳遞,因此連apply
都沒有用到,實際上確定還要考慮傳遞argument
以及上下文環境(畢竟apply須要用到this對象)。這裏的相關知識在本專欄《柯里化》和《this對象》的文章裏也有提到。本文依然堅持突出核心代碼,儘量剝離無關功能點的思路行文所以不作贅述。
慣例:若是內容有錯誤的地方歡迎指出(以爲看着不理解不舒服想吐槽也徹底沒問題);若是有幫助,歡迎點贊和收藏,轉載請徵得贊成後著明出處,若是有問題也歡迎私信交流,主頁有郵箱地址