摘要:防抖和節流嚴格算起來應該屬于性能優化的知識,但實際上遇到的頻率相當高,處理不當或者放任不管就容易引起瀏覽器卡死。
防抖和節流嚴格算起來應該屬于性能優化的知識,但實際上遇到的頻率相當高,處理不當或者放任不管就容易引起瀏覽器卡死。所以還是很有必要早點掌握的。(信我,你看完肯定就懂了)
從滾動條監聽的例子說起先說一個常見的功能,很多網站會提供這么一個按鈕:用于返回頂部。
這個按鈕只會在滾動到距離頂部一定位置之后才出現,那么我們現在抽象出這個功能需求-- 監聽瀏覽器滾動事件,返回當前滾條與頂部的距離
這個需求很簡單,直接寫:
function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log("滾動條位置:" + scrollTop); } window.onscroll = showTop
但是!
在運行的時候會發現存在一個問題:這個函數的默認執行頻率,太!高!了!。 高到什么程度呢?以chrome為例,我們可以點擊選中一個頁面的滾動條,然后點擊一次鍵盤的【向下方向鍵】,會發現函數執行了8-9次!
然而實際上我們并不需要如此高頻的反饋,畢竟瀏覽器的性能是有限的,不應該浪費在這里,所以接著討論如何優化這種場景。
防抖(debounce)基于上述場景,首先提出第一種思路:在第一次觸發事件時,不立即執行函數,而是給出一個期限值比如200ms,然后:
如果在200ms內沒有再次觸發滾動事件,那么就執行函數
如果在200ms內再次觸發滾動事件,那么當前的計時取消,重新開始計時
效果:如果短時間內大量觸發同一事件,只會執行一次函數。
實現:既然前面都提到了計時,那實現的關鍵就在于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秒以后,才會打印出滾動條位置。
到這里,已經把防抖實現了,現在給出定義:
對于短時間內連續觸發的事件(上面的滾動事件),防抖的含義就是讓某個時間期限(如上面的1000毫秒)內,事件處理函數只執行一次。
節流(throttle)繼續思考,使用上面的防抖方案來處理問題的結果是:
如果在限定時間段內,不斷觸發滾動事件(比如某個用戶閑著無聊,按住滾動不斷的拖來拖去),只要不停止觸發,理論上就永遠不會輸出當前距離頂部的距離。
但是如果產品同學的期望處理方案是:即使用戶不斷拖動滾動條,也能在某個時間間隔之后給出反饋呢?(此處暫且不論哪種方案更合適,既然產品爸爸說話了我們就先考慮怎么實現)
其實很簡單:我們可以設計一種類似控制閥門一樣定期開放的函數,也就是讓函數執行一次后,在某個時間段內暫時失效,過了這段時間后再重新激活(類似于技能冷卻時間)。
效果:如果短時間內大量觸發同一事件,那么在函數執行一次之后,該函數在指定的時間期限內不再工作,直至過了這段時間才重新生效。
實現 這里借助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)
運行以上代碼的結果是:
如果一直拖著滾動條進行滾動,那么會以1s的時間間隔,持續輸出當前位置和頂部的距離
其他應用場景舉例講完了這兩個技巧,下面介紹一下平時開發中常遇到的場景:
搜索框input事件,例如要支持輸入實時搜索可以使用節流方案(間隔一段時間就必須查詢相關內容),或者實現輸入間隔大于某個值(如500ms),就當做用戶輸入完成,然后開始搜索,具體使用哪種方案要看業務需求。
頁面resize事件,常見于需要做頁面適配的時候。需要根據最終呈現的頁面情況進行dom渲染(這種情形一般是使用防抖,因為只需要判斷最后一次的變化情況)
思考總結上述內容基于防抖和節流的核心思路設計了簡單的實現算法,但是不代表實際的庫(例如undercore js)的源碼就直接是這樣的,最起碼的可以看出,在上述代碼實現中,因為showTop本身的很簡單,無需考慮作用域和參數傳遞,所以連apply都沒有用到,實際上肯定還要考慮傳遞argument以及上下文環境(畢竟apply需要用到this對象)。這里的相關知識在本專欄《柯里化》和《this對象》的文章里也有提到。本文依然堅持突出核心代碼,盡可能剝離無關功能點的思路行文因此不做贅述。
慣例:如果內容有錯誤的地方歡迎指出(覺得看著不理解不舒服想吐槽也完全沒問題);如果有幫助,歡迎點贊和收藏,轉載請征得同意后著明出處,如果有問題也歡迎私信交流,主頁有郵箱地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108989.html
摘要:隆重請出主角防抖與節流。防抖與節流的異同相同都是防止某一時間段內,函數被頻繁調用執行,通過時間頻率控制,減少回調函數執行次數,來實現相關性能優化。參考文章分鐘理解的節流防抖及使用場景函數防抖和節流 showImg(https://segmentfault.com/img/bVburM8?w=800&h=600); 本篇課題,或許早已是爛大街的解讀文章。不過春招系列面試下來,不少伙伴們還...
摘要:若時間差大于間隔時間,則立刻執行一次函數。不同點函數防抖,在一段連續操作結束后,處理回調,利用和實現。函數防抖關注一定時間連續觸發的事件只在最后執行一次,而函數節流側重于一段時間內只執行一次。 原博客地址,歡迎star 函數防抖和節流 函數防抖和函數節流:優化高頻率執行js代碼的一種手段,js中的一些事件如瀏覽器的resize、scroll,鼠標的mousemove、mouseover...
摘要:概念函數防抖和函數節流,兩者都是優化高頻率執行代碼的一種手段。防抖任務頻繁觸發的情況下,只有任務觸發的間隔超過指定間隔的時候,任務才會執行。節流指定時間間隔內只會執行一次任務一定時間內方法只跑一次。 概念 函數防抖和函數節流,兩者都是優化高頻率執行js代碼的一種手段。 防抖:任務頻繁觸發的情況下,只有任務觸發的間隔超過指定間隔的時候,任務才會執行。 節流:指定時間間隔內只會執行一次任...
閱讀 1923·2021-10-11 10:59
閱讀 1032·2021-09-07 09:59
閱讀 2226·2021-08-27 16:17
閱讀 2783·2019-08-30 15:54
閱讀 2274·2019-08-30 12:58
閱讀 1773·2019-08-30 12:53
閱讀 1465·2019-08-28 18:13
閱讀 733·2019-08-26 13:35