UnderScore原始碼看防抖和節流

Aemple發表於2019-03-03

龜兔賽跑(快不一定好,慢也不一定差)

UnderScore原始碼看防抖和節流

相信這事一個大家都可以耳熟能詳的例子了,兔子跑得很快,這是他勝利的優勢,但是同時也是”快”讓它有了驕傲的思想,導致自己輕敵兒錯失了勝利的機會。

烏龜跑得很慢,但是他很謙虛,一步一個腳印,穩定向前抓住機遇取得了勝利!

瀏覽器裡的”龜兔賽跑”

我們在瀏覽器中時常會遇到一些高頻率事件:onscroll oninput resize onkeyup keydown… 那麼當遇到這些的時候我們到底應該讓它像”兔子一樣快速執行”,還是像”烏龜一樣穩步向前”呢? 但是如果快速執行又可能會在較大的專案或者低版本瀏覽器中造成頁面卡頓不流暢影響使用者體驗(如不理解請谷歌搜尋瀏覽器渲染過程+重繪、迴流)!而像烏龜一樣穩步向前造成卡頓的機率會小很多,而且對於使用者使用的體驗基本毫無影響!所以我們還是穩步向前為好!

UnderScore原始碼看防抖和節流

那麼怎麼做到穩步執行呢?

優化高頻事件 》》降低程式碼執行頻率

那麼怎麼做到降低程式碼執行頻率呢?

降低程式碼執行頻率 》》 throttle(節流)|| debounce(防抖)

throttle(節流)和 debounce(防抖)

UnderScore原始碼看防抖和節流

請先記住兩個概念:節流>間隔固定時間執行,防抖>你不停止我就不執行。請您記住,記住,記住!!! 不要再分不清這兩個詞語哪個是哪個了!!!!

throttle(節流)

直接上程式碼,我們們先看一個簡單版本容易理解的。

function throttle(fn, threshhold=150) {
var timeout;//方便清除定時器
var start = new Date;//開始的時間
return function () {
var context = this, args = arguments, curr = new Date() - 0
clearTimeout(timeout)//總是幹掉事件回撥(與後面的“讓方法在脫離事件後也能執行一次”對應)
if(curr - start >= threshhold){ 
    fn.apply(context, args) 
    start = curr //其實到這裡我們就已經實現了節流的邏輯
}else{
   //讓方法在脫離事件後也能執行一次(比如我們最後一次點選一個按鈕,點選後按照上面的邏輯當小於threshhold是不會執行這次點選事件的回撥函式的,所以加上這個定時器確保最後一次無論間隔時間多大都可以執行)
    timeout = setTimeout(function(){
       fn.apply(context, args) 
    }, threshhold);
   }
 }
}
var logger = throttle(function(e) {
console.log(e.pageX, e.pageY)
});

// 繫結監聽
document.querySelector("#btn").addEventListener(`click`,logger);
複製程式碼

下面看看UnderScore對throttle(節流)的封裝

function throttle(func, wait, options) {;
      let args, context, previous = 0, timeout;
      let later = function () {//最後一次定時器中的回撥
        func.apply(context, args);
        args = context = null
      }
      let throttled = function () {
        args = arguments;
        context = this;
        let now = Date.now(); // 現在的時間
        let remaning = wait - (now - previous);
        if (remaning <= 0) {
          if (timeout) {//多次連續點選的時候就清除定時器,因為它不是最後一次點選,只有最後一次點選後才會保留定時器
            clearTimeout(timeout);
            timeout = null;
          }
          func.apply(context, args);
          previous = now;
        } else if (!timeout && options.trailing !== false) {//如果我們不傳trailing那麼就是undefined同樣不等於flase
        //先判斷是否存在timeout,存在就不增加定時器,避免多次定義定時器
          timeout = setTimeout(later, remaning);
        }
      }
      return throttled;
    }
    function logger() {
      console.log(`logger`);
    }
    //引數trailiing:達到讓最後一次事件觸發後回撥方法還是能執行的效果
    btn.addEventListener(`click`, throttle(logger, 1000, { trailiing: true }));
    
    //引數leading:達到讓第一次事件觸發後不立刻執行回撥
    function throttle(func, wait, options) {
      let args, context, previous = 0, timeout;
      let later = function () {
        previous = options.leading === false ? 0 : Date.now();
        //第二步:定時器回撥中讓previous回歸正常
        func.apply(context, args);
        args = context = null
      }
      let throttled = function () {
        args = arguments;
        context = this;
        let now = Date.now();
        if (!previous && options.leading === false) previous = now;
        //第一步:使remaning一定大於0以此來達到讓它走 else if,也就是定義定時器延遲處理事件。
        let remaning = wait - (now - previous);
        if (remaning <= 0) {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          func.apply(context, args);
          previous = now;
        } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaning);
        }
      }
      return throttled;
    }
    function logger() {
      console.log(`logger`);
    }
    // btn.addEventListener(`click`, throttle(logger, 1000, { trailiing: true }));
    // 延遲第一次點選 是不生效的
    btn.addEventListener(`click`, throttle(logger, 1000, { leading: false }));
複製程式碼

此處是自己對UnderScore中throttle原始碼的簡單重寫。如有不懂的請私信我。。

debounce(防抖)

防抖實現十分簡單

function debounce(func,wait) {
      let timeout;
      return function () {
        clearTimeout(timeout); //有定時器就先清掉,始終保證只有一個定時器
        timeout = setTimeout(() => {
          func.apply(this,arguments);
          timeout = null;
        }, wait);
      }
    }
    function logger(e) {
      console.log(`logger`);
    }
    btn.addEventListener(`click`, debounce(logger,1000));
複製程式碼

但是呢,這樣又有點bug,那就是我們點選第一次也要等很久的定時器時間間隔才可以看到效果,而我們有時會希望的是點選了馬上就有效果。

下面看看UnderScore對 debounce(防抖)的封裝

function debounce(func,wait,immediate) {
      let timeout;
      return function () {
        clearTimeout(timeout);
        if(immediate){
          let callNow = !timeout;  //第一次點選的話timeout就是undefined取反就是true,就會執行下一行,第二次點選的話就timeout不為空就不會按照原來的邏輯執行了。這樣也就達到了點選第一次立即執行的效果。
          if(callNow) func.apply(this, arguments);
        }
        timeout = setTimeout(() => {
          func.apply(this,arguments);
          timeout = null;
        }, wait);
      }
    }
    function logger(e) {
      console.log(`logger`,e);
    }
    // 第三個引數 表示首次 先觸發一下
    btn.addEventListener(`click`, debounce(logger,1000,true));
複製程式碼

上面就是UnderScore對節流、防抖的基本實現了,當然還有一個取消的方法,但是那個很簡單可以自行去看一看

連結:github.com/jashkenas/u…

文章未完待續…

ToDo:(1)將文章思路再理一遍,配些動圖和例子一步步的實現一下防抖節流

ToDo:(2)將lodash的防抖節流一步步實現一遍

PS:過年在家長膘,寫得有點籠統 還望大佬們海涵

UnderScore原始碼看防抖和節流

相關文章