函式節流和去抖之間的區別

AlenQi發表於2018-02-12

函式節流和去抖都是限制基於DOM事件執行的javascript數量的方法,都是為了提高JS效能,但是兩者是有區別的。

函式節流

調節強制執行一段時間內可以呼叫函式的最大次數,如“每100毫秒最多執行一次”。 在正常情況下,我們可以在10秒鐘內呼叫此函式1000次。如果您每100毫秒將其限制為僅一次,則最多隻能執行該功能100次。

函式去抖

強制一個函式在一段時間內只被呼叫一次。如“一個函式100毫秒內只執行一次”。 也許一個函式在很集中的時間內被呼叫1000次,超過3秒,然後停止呼叫。如果我們在100毫秒內將其去抖動,該函式將僅啟動一次3.1秒。函式去抖就是對於一定時間段的連續的函式呼叫,只讓其執行一次。

重點

這些概念的一個主要用例是某些DOM事件,如滾動和調整大小。例如,如果我們將一個滾動處理程式附加到元素,並將該元素向下滾動到5000px,則可能會看到超過100個事件觸發。如果我們的事件處理程式執行一些工作(如重計算和其他DOM操作),我們可能會看到效能問題。如果你可以使執行該處理程式的次數減少,而沒有太多的中斷,那這些工作是值得的。

  • 一些小例子
  1. 等到使用者停止調整視窗大小
  2. 在使用者停止打字之前,不要觸發ajax事件
  3. 計算頁面的滾動位置,每50ms最多執行一次
  4. 在應用程式中拖動元素時,請確保良好的效能

如何實現

這兩個功能都在 UnderscoreLodash 中已經實現。當然即使我們不使用這些庫,我們也可以隨時從中抽出這些功能供自己使用。

  • throttled scroll:

    /**
     * 頻率控制 返回函式連續呼叫時,func 執行頻率限定為 次 / wait
     *
     * @param  {function}   func      傳入函式
     * @param  {number}     wait      表示時間視窗的間隔
     * @param  {object}     options   如果想忽略開始邊界上的呼叫,傳入{leading: false}。
     *                                如果想忽略結尾邊界上的呼叫,傳入{trailing: false}
     * @return {function}             返回客戶呼叫函式   
     */
    _.throttle = function(func, wait, options) {
      var context, args, result;
      var timeout = null;
      // 上次執行時間點
      var previous = 0;
      if (!options) options = {};
      // 延遲執行函式
      var later = function() {
        // 若設定了開始邊界不執行選項,上次執行時間始終為0
        previous = options.leading === false ? 0 : _.now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      };
      return function() {
        var now = _.now();
        // 首次執行時,如果設定了開始邊界不執行選項,將上次執行時間設定為當前時間。
        if (!previous && options.leading === false) previous = now;
        // 延遲執行時間間隔
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        // 延遲時間間隔remaining小於等於0,表示上次執行至此所間隔時間已經超過一個時間視窗
        // remaining大於時間視窗wait,表示客戶端系統時間被調整過
        if (remaining <= 0 || remaining > wait) {
          clearTimeout(timeout);
          timeout = null;
          previous = now;
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        //如果延遲執行不存在,且沒有設定結尾邊界不執行選項
        } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
        }
        return result;
      };
    };
    複製程式碼
  • debounced resize:

    /**
     * 空閒控制 返回函式連續呼叫時,空閒時間必須大於或等於 wait,func 才會執行
     *
     * @param  {function} func        傳入函式
     * @param  {number}   wait        表示時間視窗的間隔
     * @param  {boolean}  immediate   設定為ture時,呼叫觸發於開始邊界而不是結束邊界
     * @return {function}             返回客戶呼叫函式
     */
    _.debounce = function(func, wait, immediate) {
      var timeout, args, context, timestamp, result;
    
      var later = function() {
        // 據上一次觸發時間間隔
        var last = _.now() - timestamp;
    
        // 上次被包裝函式被呼叫時間間隔last小於設定時間間隔wait
        if (last < wait && last > 0) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          // 如果設定為immediate===true,因為開始邊界已經呼叫過了此處無需呼叫
          if (!immediate) {
            result = func.apply(context, args);
            if (!timeout) context = args = null;
          }
        }
      };
    
      return function() {
        context = this;
        args = arguments;
        timestamp = _.now();
        var callNow = immediate && !timeout;
        // 如果延時不存在,重新設定延時
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
          result = func.apply(context, args);
          context = args = null;
        }
    
        return result;
      };
    };
    複製程式碼

應用場景

  • 函式節流

    • DOM 元素的拖拽功能實現(mousemove)
    • 計算滑鼠移動的距離(mousemove)
    • 搜尋聯想(keyup)
    • 監聽滾動事件判斷是否到頁面底部自動載入更多內容
  • 函式去抖

    • 視窗縮放,每次resize/scroll觸發事件
    • 文字輸入的驗證(連續輸入文字後傳送 AJAX 請求進行驗證,驗證一次就好)

相關文章