解析underscore中的throttle

什麼是throttle(節流)

Throttling enforces a maximum number of times a function can be called over time.java

簡單來講就是你假設給定一個wait表示這在個時間內該函數最多能夠被執行一次。咱們知道知道瀏覽器scroll觸發事件的頻率很是高,若是不使用節流的話,咱們輕輕一滾動鼠標滑輪可能就觸發了10來次某個添加到scroll事件的函數。但若是咱們使用節流這個技術的話,咱們設置wait爲1000(ms),當咱們不停地滾動滑輪10s,函數最多被執行10次。10000 / 1000 = 10瀏覽器

最簡單的節流

var throttle = function(func, wait){
    var previous = 0;
    return function(){
        var now = +new Date();
        if (now - previuos > wait){
            func.apply(this, arguments);
            last = now;
        }
    }
}

這個函數利用閉包返回一個函數,並且它有兩個重要的特色:閉包

  1. 當兩次函數觸發的時間間隔大於wait時,func纔會被調用
  2. 第一次觸發時func會被調用

underscore中的throttle

// Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var timeout, context, args, result;
    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;
    };

    var throttled = function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;

      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          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;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  };

咋一看這個函數的實現比當初那個簡單的函數長了不少, 別怕由於他們的思想是如出一轍的,多餘的代碼只是爲了一些額外的特性,並不複雜。app

首先多了一個options參數,它是一個對象,能夠設置leadingtrailing屬性。leading是提早領先的意思,在那個簡單的版本中咱們知道函數在第一次觸發時候func是會被觸發的,這就是leading。因此當咱們沒有設置{leading: false}時候,func會在第一次函數觸發時候立刻被執行。可是當咱們顯性地傳入{leading: false}時候,func就不會立刻執行。這是由於if (!previous && options.leading === false) previous = now; 開始previous爲0那麼條件均爲真,previous = nownow - previous > wait不成立。
即第一次觸發函數會進入到函數

else if (!timeout && options.trailing !== false) {
    // var remaining = wait - (now - previous);
    // now = previous;所以later會在wait毫秒後被執行
    timeout = setTimeout(later, remaining);
}

再來看看laterthis

var later = function() {
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
};
其實寫成這樣更號理解
var later = function() {
    previous = options.leading === false ? 0 : _.now();
    // 爲了讓將previous設爲0,是讓if (!previous && options.leading === false)再次成立
    // 意思就是當超過wait的時間沒去觸發函數了,再次觸發時候的此次也算是首次,它不能立刻被執行。(想象就是不斷滑動滾輪10s,而後放下鼠標去喝口水,再回來滑滾輪,那應該算做新的一次開始,而不是上次的繼續)
    result = func.apply(context, args);
    timeout =  context = args = null;
};

可是若是第二次觸發與第一次觸發的時間間隔大於wait時候就會進入到線程

// 實際上remaining<=0就足夠了,後者是考慮到假如客戶端修改了系統時間則立刻執行func函數
if (remaining <= 0 || remaining > wait) {
    // 取消第一次的setTimeout
    if (timeout) {
        clearTimeout(timeout);
        timeout = null;
    }
    previous = now;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
}
其實也應該寫成這樣更好理解
if (remaining <= 0 || remaining > wait) {
    if (timeout) {
        clearTimeout(timeout);
    }
    previous = now;
    result = func.apply(context, args);
    timeout = context = args = null;
}

有個疑問就是imeout = setTimeout(later, remaining), remaining等於wait,若是兩次時間間隔十分接近wait的又大於wait應該是怎麼樣的流程呢。我的以爲應該是進入到上面這個代碼塊而後clearTimeout, 爲何呢,首先javaScript是單線程的,setTimeout的意思是將函數在wait毫秒後添加到任務隊列中,而不是當即執行。因此理論上來說仍是進入上述代碼塊要比在執行later()早。可是想一想若是每次都是setTimeout也行,每隔wait運行later,效果差很少。code

小結

因此第三個參數不傳入就是leading模式。
{trailing: false}也是leading模式但和不傳參數仍是有點區別就是它沒法執行timeout = setTimeout(later, remaining);orm

{leading: false}就是trailing模式,他的timeout = setTimeout(later, remaining);其實是timeout = setTimeout(later, wait)對象

相關文章
相關標籤/搜索