函式的防抖與節流

jilaokang發表於2018-10-10

最初接觸到函式的防抖與節流是在lodash庫中看到了這樣一個函式throttle,當時的心理感受是mmp,這是啥,一查翻譯,哦...原來,還是不懂。防抖與節流看著很高階,其實在日常的程式設計中也是經常遇到,值得我們擁有。

函式防抖(debounce)

當事件持續被觸發時,並不立即執行事件處理函式,而是等到約定的時間後再執行,當約定的時間到來之前,又一次觸發了事件則重新進行計時。

  • 函式防抖的簡單實現
//arguments 為非箭頭函式中可用的內部變數,為傳遞給函式引數的偽陣列。
const debounce = function(fn, wait) {
  let timer = null;
  return function handler() {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, wait)
  }
}
複製程式碼

通過 fn.apply 呼叫 fn 是為了能夠保持函式 handler 呼叫者的 this 上下文,而不是指向 window。來看下面的例子:

  function calcCirleArea(arg) {
    const r = this.r; // 10
    const area = 3.14 * r * r; // 314
    return area;
  }

  const calcArea = debounce(calcCirleArea, 1000);
  const circle = {
    r: 10,
    calcArea
  };
  circle.calcArea();
複製程式碼

執行 circle.calcArea(),在函式 calcCirleArea 中可以訪問到物件 circle 的屬性 r,原因就是通過 fn.apply 呼叫時繫結了 circle 的上下文。假如我們通過fn()直接呼叫,在非嚴格模式下 calcCirleArea 函式 this 的指向為 window,this.r為 undefined,所以需要通過 fn.apply 來呼叫。

  • 防抖函式應用場景

    比如在註冊使用者的時候,驗證密碼是否符合規範,我們並不需要在使用者輸入時頻繁地去驗證,而是等到使用者最後一次觸發輸入後,等待一定時間沒有再輸入,此時認為輸入已經結束,可以進行驗證了,這是符合邏輯的,也提升了效能。

    無防抖效果:

    no debounce 防抖效果: debounce

函式節流(throttle)

當事件持續被觸發時,保證函式在一定時間內只執行一次事件處理函式。

  • 函式節流的簡單實現

    函式節流的實現主要有時間軸的方法和計時器的方法。時間軸的方法是通過比較事件觸發時間與上一次函式執行時間(第一次為 0,保證第一次一定執行)的差 dt 來判斷是否執行事件處理函式,如果 dt 大於約定的時間則執行,反之則不執行;計時器的方法是在事件觸發時,如果當前沒有計時器則設定計時器來觸發事件處理函式的執行。這兩種方法對於最後一次觸發事件,都有可能不會執行。

    1. 時間軸
      const throttle = function(fn, wait) {
      //pre設定為0使得第一次一定執行
      let pre = 0;
      return function() {
        const now = Date.now();
        const context = this;
        const args = arguments;
        if (now - pre > wait) {
          fn.apply(context, args);
          pre = now;
        }
        }
      }
    複製程式碼
    1. 計時器
      const throttle = function(fn, wait) {
        let timer = null;
        return function() {
          const context = this;
          const args = arguments;
          clearTimeout(timer);
          if (!timer) {
            timer = setTimeout(function() {
              fn.apply(context, args);
              timer = null;
            }, wait)
          }
        }
      }
    複製程式碼

有些時候我們希望至少第一次和最後一次觸發事件得到響應,這就可以結合時間軸和計時器的方法。 如下,當事件最後一次觸發時要麼達到了約定的時間可以立即執行事件處理函式,要麼設定一個 timer 等待約定的時間後執行。

  const throttle = function(fn, wait) {
    let pre = 0;
    let timer = null;
    return function() {
      const context = this;
      const args = arguments;
      const now = Date.now();
      clearTimeout(timer);
      const dt = now - pre;
      if (dt > wait) {
        fn.apply(context, args);
        pre = now;
      } else {
        timer = setTimeout(function() {
          fn.apply(context, args);
        }, wait)
      }
    }
  }
複製程式碼
  • 節流函式應用場景

    比如在客戶端搜尋,伺服器返回搜尋結果時我們希望儘快重新整理搜尋結果,但又不希望頻繁的向伺服器請求結果而導致效能下降,節流就是一個很好的選擇。

    無節流效果: no throttle

    節流效果: throttle

總結

  • 防抖和節流都是用來控制函式的頻繁觸發,提升效能。
  • 在頻繁觸發事件的情況下防抖有可能不執行處理函式,而節流在事件觸發後,一定會執行一次。
  • 為了能夠在第一次和最後一次觸發事件時執行處理函式,可以結合時間軸和計時器的方法。

相關文章