防抖函數Debounce實現

實現原理就是利用定時器,函數第一次執行時設定一個定時器,之後調用時發現已經設定過定時器就清空之前的定時器,並重新設定一個新的定時器,如果存在沒有被清空的定時器,當定時器計時結束後觸發函數執行。

防抖函數Debounce實現防抖函數Debounce實現

手寫一個 debounce

防抖函數 debounce 指的是某個函數在某段時間內,無論觸發了多少次回調,都只執行最後一次。

// fn 是需要防抖處理的函數 
// wait 是時間間隔 
function debounce(fn, wait = 50) { 
    // 通過閉包緩存一個定時器 id 
    let timer = null 
    // 將 debounce 處理結果當作函數返回 
    // 觸發事件回調時執行這個返回函數 
    return function(...args) { 
        // this保存給context 
        const context = this 
       // 如果已經設定過定時器就清空上一次的定時器 
        if (timer) clearTimeout(timer) 
       
       // 開始設定一個新的定時器,定時器結束後執行傳入的函數 fn 
        timer = setTimeout(() => { 
            fn.apply(context, args) 
        }, wait) 
    } 
} 
 
// DEMO 
// 執行 debounce 函數返回新函數 
const betterFn = debounce(() => console.log('fn 防抖執行了'), 1000) 
// 停止滑動 1 秒後執行函數 () => console.log('fn 防抖執行了') 
document.addEventListener('scroll', betterFn)

不過 underscore 中的 debounce 還有第三個參數:immediate 。這個參數是做什麼用的呢?

傳參 immediate 爲 true, debounce會在 wait 時間間隔的開始調用這個函數 。(注:並且在 wait 的時間之內,不會再次調用。)在類似不小心點了提交按鈕兩下而提交了兩次的情況下很有用。

把 true 傳遞給 immediate 參數,會讓 debounce 在 wait 時間開始計算之前就觸發函數(也就是沒有任何延時就觸發函數),而不是過了 wait 時間才觸發函數,而且在 wait 時間內也不會觸發(相當於把 fn 的執行鎖住)。如果不小心點了兩次提交按鈕,第二次提交就會不會執行。

那我們根據 immediate 的值來決定如何執行 fn 。如果是 immediate 的情況下,我們立即執行 fn ,並在 wait 時間內鎖住 fn 的執行, wait 時間之後再觸發,纔會重新執行 fn ,以此類推。

// immediate 表示第一次是否立即執行 
function debounce(fn, wait = 50, immediate) { 
    let timer = null 
    return function(...args) { 
        // this保存給context 
        const context = this 
        if (timer) clearTimeout(timer) 
       
       // immediate 爲 true 表示第一次觸發後執行 
       // timer 爲空表示首次觸發 
        if (immediate && !timer) { 
            fn.apply(context, args) 
        } 
        
        timer = setTimeout(() => { 
            fn.apply(context, args) 
        }, wait) 
    } 
} 
 
// DEMO 
// 執行 debounce 函數返回新函數 
const betterFn = debounce(() => console.log('fn 防抖執行了'), 1000, true) 
// 第一次觸發 scroll 執行一次 fn,後續只有在停止滑動 1 秒後才執行函數 fn 
document.addEventListener('scroll', betterFn)
underscore 源碼解析

看完了上文的基本版代碼,感覺還是比較輕鬆的,現在來學習下 underscore 是如何實現 debounce 函數的,學習一下優秀的思想,直接上代碼和註釋,本源碼解析依賴於 underscore 1.9.1 版本實現。

// 此處的三個參數上文都有解釋 
_.debounce = function(func, wait, immediate) { 
  // timeout 表示定時器 
  // result 表示 func 執行返回值 
  var timeout, result; 
 
  // 定時器計時結束後 
  // 1、清空計時器,使之不影響下次連續事件的觸發 
  // 2、觸發執行 func 
  var later = function(context, args) { 
    timeout = null; 
    // if (args) 判斷是爲了過濾立即觸發的 
    // 關聯在於 _.delay 和 restArguments 
    if (args) result = func.apply(context, args); 
  }; 
 
  // 將 debounce 處理結果當作函數返回 
  var debounced = restArguments(function(args) { 
    if (timeout) clearTimeout(timeout); 
    if (immediate) { 
      // 第一次觸發後會設置 timeout, 
      // 根據 timeout 是否爲空可以判斷是否是首次觸發 
      var callNow = !timeout; 
      timeout = setTimeout(later, wait); 
      if (callNow) result = func.apply(this, args); 
    } else { 
     // 設置定時器 
      timeout = _.delay(later, wait, this, args); 
    } 
 
    return result; 
  }); 
 
  // 新增 手動取消 
  debounced.cancel = function() { 
    clearTimeout(timeout); 
    timeout = null; 
  }; 
 
  return debounced; 
}; 
 
// 根據給定的毫秒 wait 延遲執行函數 func 
_.delay = restArguments(function(func, wait, args) { 
  return setTimeout(function() { 
    return func.apply(null, args); 
  }, wait); 
});

相比上文的基本版實現,underscore 多了以下幾點功能。

  1. 1、函數 func 的執行結束後返回結果值 result
  1. 2、定時器計時結束後清除 timeout ,使之不影響下次連續事件的觸發
  1. 3、新增了手動取消功能 cancel
  1. 4、immediate 爲 true 後只會在第一次觸發時執行,頻繁觸發回調結束後不會再執行

本文地址:https://www.linuxprobe.com/anti-shake-function-implementation.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章