函式節流、函式防抖實現原理分析

FREAKFILTH發表於2017-02-21

之前翻譯了一篇部落格,裡面有講到這個,今天單獨拎出來聊聊。

前言

事件的觸發權很多時候都屬於使用者,有些情況下會產生問題:

  • 向後臺傳送資料,使用者頻繁觸發,對伺服器造成壓力

  • 一些瀏覽器事件:window.onresizemousemove等,觸發的頻率非常高,會造成瀏覽器效能問題

如果你碰到這些問題,那就需要用到這些技術了。

我們先來解釋一下函式節流(Throttling)和函式防抖(Debouncing)的區別:

我們上班、生活每天都需要坐電梯,用這個比喻再恰當不過了:

函式防抖和我們平時坐電梯差不多,如果有人進電梯(使用者觸發事件),那將在10秒鐘後出發(執行程式),這時如果又有人進電梯了(使用者在10秒內再次觸發事件),我們又得等10秒再出發(重新計時)。

函式節流就比較直觀了,有人進電梯,就開始計時,每10秒運送一次,如果沒有人,則待機。

這兩種策略具體使用場景還得看你的實際需求了,但是,只要理解了這個思想,接下來的就好辦了。

函式節流(Throttling)

函式節流的作用上面講的很清晰了,接下來我們分析一下如何實現它:

var throttle = function(fn, interval) { //fn為要執行的函式,interval為延遲時間
  var _self = fn,  //儲存需要被延遲執行的函式引用
      timer,  //定時器
      firstTime = true;  //是否第一次呼叫

  return function() { //返回一個函式,形成閉包,持久化變數
    var args = arguments, //快取變數
        _me = this;

    if(firstTime) { //如果是第一次呼叫,不用延遲執行
      _self.apply(_me, args);
      return firstTime = false;
    }

    if(timer) { //如果定時器還在,說明上一次延遲執行還沒有完成
      return false;
    }

    timer = setTimeout(function() { //延遲一段時間執行
      clearTimeout(timer);
      timer = null;
      _self.apply(_me, args);
    }, interval || 500);
  };
};

//使用
window.onresize = throttle(function() {
  //你要執行的程式碼
}, 500);複製程式碼

其實函式節流和函式防抖的關鍵就是對setTimeout的運用,說個題外話,當你對setTimeoutsetInterval內部的運作原理徹底瞭解後,你就是一名JS大神了。?

函式防抖(Debouncing)

我個人在開發中比較喜歡使用函式防抖策略,其實也說不上誰好,適應的場景不同。

我把註解也寫在程式碼中了:

function debounce(fn, interval, immediate) {
  //fn為要執行的函式
  //interval為等待的時間
  //immediate判斷是否立即執行
  var timeout;  //定時器

  return function() { //返回一個閉包
    var context = this, args = arguments; //先把變數快取
    var later = function() {  //把稍後要執行的程式碼封裝起來
      timeout = null; //成功呼叫後清除定時器
      if(!immediate) fn.apply(context, args); //不立即執行時才可以呼叫
    };

    var callNow = immediate && !timeout;  //判斷是否立即呼叫,並且如果定時器存在,則不立即呼叫
    clearTimeout(timeout);  //不管什麼情況,先清除定時器,這是最穩妥的
    timeout = setTimeout(later, interval);  //延遲執行
    if(callNow) fn.apply(context, args);  //如果是第一次觸發,並且immediate為true,則立即執行
  };
};

//使用
var myEfficientFn = debounce(function() {
  //你要做的事
}, 250);

window.addEventListener('resize', myEfficientFn);複製程式碼

上面程式碼有一個巧妙的設計:var callNow = immediate && !timeout;,判斷了timeout,如果存在,說明有定時器在執行,那就不是第一次執行,則不執行if(callNow)裡的程式碼了。

總結

這兩個程式碼塊是在開發中經常使用的,無論是為了適應需求還是優化效能,我們都沒有理由不適用它們。

另外在jQuery的原始碼中也使用了這種技巧,即便在這個框架橫行的時代,還是隻有底層的知識能讓我感到踏實。

喜歡本文的朋友可以關注我的微信公眾號,不定期推送一些好文。

函式節流、函式防抖實現原理分析

本文出自Rockjins Blog,轉載請與作者聯絡。否則將追究法律責任。

相關文章