高頻:手寫一個節流函數 Throttle
debounce 與 throttle 是開發中常用的高階函數,作用都是為了防止函數被高頻調用,換句話說就是,用來控制某個函數在一定時間內執行多少次。
使用場景
比如綁定響應鼠標移動、窗口大小調整、滾屏等事件時,綁定的函數觸發的頻率會很頻繁。若稍處理函數微復雜,需要較多的運算執行時間和資源,往往會出現延遲,甚至導致假死或者卡頓感。為了優化性能,這時就很有必要使用 debounce 或 throttle 了。
debounce 與 throttle 區別
防抖 (debounce) :多次觸發,只在最后一次觸發時,執行目標函數。
節流(throttle):限制目標函數調用的頻率,比如:1s內不能調用2次。
手寫一個 throttle
實現方案有以下兩種:
- 第一種是用時間戳來判斷是否已到執行時間,記錄上次執行的時間戳,然后每次觸發事件執行回調,回調中判斷當前時間戳距離上次執行時間戳的間隔是否已經達到時間差(Xms) ,如果是則執行,并更新上次執行的時間戳,如此循環。
- 第二種方法是使用定時器,比如當 scroll 事件剛觸發時,打印一個 hello world,然后設置個 1000ms 的定時器,此后每次觸發 scroll 事件觸發回調,如果已經存在定時器,則回調不執行方法,直到定時器觸發,handler 被清除,然后重新設置定時器。
這里我們采用第一種方案來實現,通過閉包保存一個 previous 變量,每次觸發 throttle 函數時判斷當前時間和 previous 的時間差,如果這段時間差小于等待時間,那就忽略本次事件觸發。如果大于等待時間就把 previous 設置為當前時間并執行函數 fn。
我們來一步步實現,首先實現用閉包保存 previous 變量。
- const throttle = (fn, wait) => {
- // 上一次執行該函數的時間
- let previous = 0
- return function(...args) {
- console.log(previous)
- ...
- }
- }
執行 throttle 函數后會返回一個新的 function ,我們命名為 betterFn 。
- const betterFn = function(...args) {
- console.log(previous)
- ...
- }
betterFn 函數中可以獲取到 previous 變量值也可以修改,在回調監聽或事件觸發時就會執行 betterFn ,即 betterFn(),所以在這個新函數內判斷當前時間和 previous 的時間差即可。
- const betterFn = function(...args) {
- let now = +new Date();
- if (now - previous > wait) {
- previous = now
- // 執行 fn 函數
- fn.apply(this, args)
- }
- }
結合上面兩段代碼就實現了節流函數,所以完整的實現如下。
- // fn 是需要執行的函數
- // wait 是時間間隔
- const throttle = (fn, wait = 50) => {
- // 上一次執行 fn 的時間
- let previous = 0
- // 將 throttle 處理結果當作函數返回
- return function(...args) {
- // 獲取當前時間,轉換成時間戳,單位毫秒
- let now = +new Date()
- // 將當前時間和上一次執行函數的時間進行對比
- // 大于等待時間就把 previous 設置為當前時間并執行函數 fn
- if (now - previous > wait) {
- previous = now
- fn.apply(this, args)
- }
- }
- }
- // DEMO
- // 執行 throttle 函數返回新函數
- const betterFn = throttle(() => console.log('fn 函數執行了'), 1000)
- // 每 10 毫秒執行一次 betterFn 函數,但是只有時間差大于 1000 時才會執行 fn
- setInterval(betterFn, 10)