throttle與debounce算法的邏輯

throttledebounce,是兩個算法。兩者處理的對象,都是那些需要被反復調用的函數,特別是回調函數。其目的,都是控制函數重複執行的頻率,避免性能下降。
throttle的應用場景如瀏覽器窗口的resize,debounce的應用場景如搜索框的suggestion。
throttle的基本思想很簡單,通過封裝(包裹),使原函數相鄰兩次執行時間必須大於一個值interval。如果第二次調用函數的時機,沒有超出interval,則忽略之,或者用setTimeout使函數在超出interval的某個時機再執行原函數
debounce的思路也很簡單,通過封裝(包裹),使函數第一次調用時,不馬上執行原函數,而是設置一個timeout。如果在timeout等待過程中,繼續調用這個函數,則取消現在的timeout,并重新設置一個。
但是underscore庫中的這兩個函數,加了一些參數,使得邏輯非常複雜。即使每個函數的核心代碼就20行左右,我也要花兩三天時間才能理順。

throttle

  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      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 > wait,在人為將系統時間調慢的情況下,成立
      if (remaining <= 0 || remaining > wait) {
        //由於setTimeout存在最小時間精度問題,因此有可能wait階段已過,但之前設置的setTimeout操作還未執行
        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;
    };
  };

該函數的第三個參數options,有兩個屬性leadingtrailing,都是布爾值。他們能形成四種組合形式:

  1. {"trailing": true,"leading": true},是為默認值
  2. {"trailing": true,"leading": false}
  3. {"trailing": false,"leading": true}
  4. {"trailing": false,"leading": false}

這個參數會影響程序的流程,現以流程圖分述之。
下為{"trailing": true,"leading": true}的情況:
true/true
下為{"trailing": true,"leading": false}的情況:
true/false
下為{"trailing": false,"leading": true}的情況:
false,true
下為{"trailing": false,"leading": false}的情況:
false,false
leadingtrailing這兩個科技術語,一般是用來表示「首」和「尾」。但是通過上面流程圖的演示,可知在這段程序中,他們各自代表的意義,並非典型的前與後的對立關係。外國程序員在用詞方面的瑕疵,也給我們的閱讀造成了一定的困擾。

debounce

_.debounce = function(func, wait, immediate) {// immediate默認為false
    var timeout, args, context, timestamp, result;
    var later = function() {
        var last = _.now() - timestamp;//如果在wait期間,函數被調用,導致timestamp被更新,則原函數始終不會被執行,直到函數停止被調用
        if (last < wait && last >= 0) {//系統時間調慢之後,則last < 0
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };

    return function() {
        context = this;
        args = arguments;
        timestamp = _.now();//此處是重點,每次都更新timestamp,這個值會影響到後面later函數的執行
        var callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {//第一次調用該函數,或者上一個timeout已經被執行完畢,且immediate為true,則立即執行func函數
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
};

immediate這個參數,目的在於,在不存在timeout的情況下,先同步執行一次原函數,然後再設置一個timeout
以下是流程圖:
debounce

測試

除了上面的源代碼,我也在自己的Javascript庫中實現了這兩個函數,沒有那麼多的參數,邏輯更加清楚。
為了測試這兩個函數,我寫了一段簡單的代碼:

var todo = function() {
    var now = new Date();
    console.log(now.getSeconds() + "," + now.getMilliseconds());
};
var callback = debounce(todo, 1230); //事件的響應函數,參數1230ms
var interval = setInterval(function() {
    callback();
}, 100); //事件每100ms觸發一次
setTimeout(function() {
    clearTimeout(interval);
}, 6 * 1000); //事件持續6s
posted @ 2016-01-13 14:23  嘯嘯生  阅读(785)  评论(2编辑  收藏  举报