天天看點

快速了解_.debounce方法

首先:

快速了解_.debounce方法

  先談談類似上圖的需求,有可能需要實作使用者在輸入内容的時候,使用ajax實時傳輸内容到伺服器。

我們肯定不希望看到,每次輸入都請求一次,如此頻繁的請求,恐怕對身體也不是很好吧?

其次:

解決的思路之一:underscore.js類庫中的_.debounce方法。關于什麼是underscore,什麼是函數式程式設計等等,我覺得有必要了解下。

說正事:_.debounce方法的作用是防抖動,這就是傳說中的函抖術,當你的事件在不斷觸發的時候,會根據你設定的間隔時間隻觸發一次回調

像這樣:

<body>
    <input type="text" id="chenjian" style="width:200px;height:50px"></input>
</body>
<script type="text/javascript" src="underscore.js"></script>
<script>
    var chenjian = document.getElementById(\'chenjian\');
    function jj (){
        console.log(this.value);
    }
    chenjian.addEventListener(\'keyup\',_.debounce(jj,500));
  </script>      

  當我不斷觸發鍵盤事件,如果我觸發事件的時間間隔低于500毫秒,則不會調用jj函數,這樣限制了頻率,就不會對身體不好啦。

用起來倒是很友善,相對應的還有 _.throttle 方法。

接下來我們看看 debounce  的源碼吧 

_.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
      var last = _.now() - timestamp;

      if (last < wait && 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();
      var callNow = immediate && !timeout;
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };
      

 下面是我自己有注釋的重寫,省略了第三個參數  immediate  ,不過對了解問題不大。

/* fnc 是傳入的函數
 * wait 是設定的時間間隔
 */

function debounce(fnc,wait){
 //time1 實時更新的事件觸發時間
 //context 存放this
 //args 存放參數
  //time2 最近一次觸發事件離最後一個定時器執行之間 間隔的時長
 //result 存放回調函數的結果
 // timer 定時器
  var time1, context, args, time2, result, timer;

  function cj(){
    //觸發事件後,過了設定的時間間隔wait,然後進入cj
    //
    console.log(\'調用了cj函數\')

    var time2 = _.now() - time1;//調用cj函數時,離你最近一次觸發事件過了time2的時間
    console.log(\'調用cj函數時,離你最近一次觸發事件過了time2的時間:\' + ( time2));
    if(time2 < wait && time2 > 0 ) {
      //如果時間差小于設定的間隔那就說明還沒到時間去調用fnc
      //重新注冊一個定時器,到規定的時間去調用func,這裡有個問題,為什麼不直接去調用fnc,而是調用cj本身
      //調用fnc的後果就是到了時間調用fnc後,之後就不會調用了,因為程序裡面隻注冊了一個定時器
      //如果換成cj,那麼其實一直在注冊定時器的,實際上就是:我希望在監聽到鍵盤事件後的2s之後調用回調函數,在這2s之内如果
      //事件又被觸發了,不執行回調,因為這樣就避免了一監聽到就調用帶來的損耗
      //實作的原理就像源碼注釋中:難點就是如何在正确的時間調用回調
      //你第一次觸發事件的時候,我記下時間并且注冊一個定時器(實際上每次觸發事件,都會更新time1),然後在你設定的間隔時間之後去調用回調。
      // 但是在你設定的時間間隔這段時間内,你有可能會繼續觸發事件(注意,這時你原先注冊的定時器還是會正常運作的),你不斷觸發事件
      //需要不斷對你觸發事件的時間進行判斷,以此來修正下次回調執行的時間
      //判斷最近一次的
      // 事件觸發距離這個定時器
      //          是過了多久,如果沒有超過設定的
      //          500ms,那就會在比如還剩300ms的地方注冊一個定時器
      //         這個定時器就會正确的在500ms後執行,
      //其實就是在不斷修正fnc調用的時間。  
      timer = setTimeout(cj,wait - time2);
    } else {
      //說明是時候調用func了,已經過了或者剛好到了設定的間隔
      timer = null;//定時器置空一下
      result = fnc.apply(context, args);//綁定this指向
      if (!timer){
          context = args = null;
      }
    }
  }
  return function(){
    //每次觸發事件都會執行
    console.log(\'觸發了事件\');
    time1 = _.now();//現在的時間
    context = this;//是指調用這個方法的上下文
    args = arguments;//方法傳入的參數,即debounce函數的第一個函數的參數
    if(!timer){//如果不存在定時器,就注冊一個定時器,在設定的時間間隔後調用cj
        timer = setTimeout(cj,wait);
    }
      //如果存在了定時器說明還沒到設定的時間間隔,就又觸發了一次事件,此時要做的處理
      //就是不做任何操作

    return result;

  }

}
      

 看得有點暈了。。。一句話總結: 

  最後一次執行的定時器 根據 最後一次觸發事件的時間 距離自己的時間間隔來判斷,是繼續設定一個定時器來滿足延遲500ms後執行,還是立刻執行。

打個比方,我兩約定:原本不動的我會在你停止不動2s後開始跑步。我的任務就是隻要你不動的時候我就開始計時,隻要過了2s,我就可以跑路了。但你有可能停下來,隻過了1s你就又開始動起來了。我肯定不能跑,因為這是按照約定。 在這裡約定中的我開始跑步就是 函數中的 fnc 函數 ,2s就是wait ,我的任務就是定時器的任務,就是 JJ 函數。你不動就是指觸發了事件。

其中個人覺得的了解難點就是,如何去實作確定你不動2s後我開始跑步,源碼中就是通過你不動的那個時間點來修正我下一次開始跑步的時間點。

額。。。大概意思就是這樣。有啥問題還請有緣人指點。