高階函式技巧-函式防抖與節流

weixin_34253539發表於2017-12-19

在實際開發中,函式一定是最實用最頻繁的一部分,無論是以函式為核心的函數語言程式設計,還是更多人選擇的物件導向式的程式設計,都會有函式的身影,所以對函式進行深入的研究是非常有必要的。


函式節流

比較直白的說,函式節流就是強制規定一個函式在一段時間內能夠被執行的最大次數,比如,規定某個函式在每100毫秒的時間段內,最多被執行一次,那麼對應的在10s(10000ms)內,最多就會執行100(10000ms/100ms)次

這裡節流和防抖的概念比較容易搞混,所以文中許多關鍵性定義,參考此處文件翻譯過來,移步到the-difference-between-throttling-and-debouncing

在瀏覽器中,頻繁的DOM操作非常消耗記憶體和CPU時間,比如監聽了resize,touchmove,scroll...等事件,在dom改變時都會不斷觸發回撥。現在的react 和 vue 等前端框架都提出了虛擬DOM的概念,會把多次DOM操作合併到一次真實操作中,就是使用了Diff演算法,這樣就大大減低了DOM操作的頻次。但是,這裡並不是要討論diff演算法,如果感興趣可以戳上面的連結,而是解釋如何利用setTimeout來減低DOM頻繁操作的風險。

最早接觸到這個概念的時候,是在《高程3》最後幾章上面。

函式節流背後的基本思想是指,某些程式碼不可以在沒有間斷的情況下連續重複執行.第一次呼叫函式,建立了一個定時器,在指定的時間間隔之後執行程式碼。當第二次呼叫該函式時,它會清除前一次的定時器並設定另一個。

封裝方法也比較簡單,書中對此問題也進行了處理:

  function throttle(method,context) {
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
       method.call(context)
      },1000)
  }

使用定時器,讓函式延遲1秒後執行,在此1秒內,然後throttle函式再次被呼叫,則刪除上次的定時器,取消上次呼叫的佇列任務,重新設定定時器。這樣就可以保證1秒內函式只會被觸發一次,達到了函式節流的目的

可以利用 resize事件測試一下;

   var  i = 0;
   function handler(){
       console.log(i++);
    }
   window.onresize = function(){
       throttle(handler,window)
   }

可以發現,在瀏覽器的除錯模式下,切換橫屏/豎屏,只觸發了一次

chrome 除錯

函式防抖

函式防抖 規定函式再次執行需要滿足兩個條件:

  • 1,必須要等待一段時間;
  • 2,在條件1等待的時間段內不再被觸發,一旦在條件1等待的時間內再次被觸發,等待時間就要重新開始計算。

比如:對一個函式加了100ms的防抖操作,然後在3s(3000ms)時間段內,這個函式被不連續的呼叫了1000次,3s後停止呼叫。 它只會在3100ms的時刻執行一次。

具體實現程式碼,看下 underscore.js中的 _.debounce 原始碼:

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.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;
  };
};

wait引數代表debounce時間, _.now()返回當前時間的時間戳,同樣以ms 為單位。 如果傳入了 immediate,會立即觸發回撥函式。

應用場景:

參考資料:

相關文章