1. 程式人生 > >throttle & debounce(節流&防抖)

throttle & debounce(節流&防抖)

轉載自https://www.cnblogs.com/wilber2013/p/5893426.html

白話debounce和throttle

遇到的問題

在開發過程中會遇到頻率很高的事件或者連續的事件,如果不進行效能的優化,就可能會出現頁面卡頓的現象,比如:

  1. 滑鼠事件:mousemove(拖曳)/mouseover(劃過)/mouseWheel(滾屏)
  2. 鍵盤事件:keypress(基於ajax的使用者名稱唯一性校驗)/keyup(文字輸入檢驗、自動完成)/keydown(遊戲中的射擊)
  3. window的resize/scroll事件(DOM元素動態定位)

為了解決這類問題,常常使用的方法就是throttle(節流)

debounce(去抖)。throttle(節流)和debounce(去抖)都是用來控制某個函式在一定時間內執行多少次的解決方案,兩者相似而又不同。

下面就具體的看看兩者的相似和區別。

認識throttle和debounce

throttle和debounce的作用就是確認事件執行的方式和時機,以前總是不太清楚兩者的區別,容易把二者弄混。

下面就通過兩個簡單的場景描述一下debounce和throttle,以後想到這兩個場景就不會再弄混了:

debounce
假設你正在乘電梯上樓,當電梯門關閉之前發現有人也要乘電梯,禮貌起見,你會按下開門開關,然後等他進電梯; 
如果在電梯門關閉之前,又有人來了,你會繼續開門;
這樣一直進行下去,你可能需要等待幾分鐘,最終沒人進電梯了,才會關閉電梯門,然後上樓。

所以debounce的作用是,當呼叫動作觸發一段時間後,才會執行該動作,若在這段時間間隔內又呼叫此動作則將重新計算時間間隔

throttle
假設你正在乘電梯上樓,當電梯門關閉之前發現有人也要乘電梯,禮貌起見,你會按下開門開關,然後等他進電梯;  
但是,你是個沒耐心的人,你最多隻會等待電梯停留一分鐘;
在這一分鐘內,你會開門讓別人進來,但是過了一分鐘之後,你就會關門,讓電梯上樓。

所以throttle的作用是,預先設定一個執行週期,當呼叫動作的時刻大於等於執行週期則執行該動作,然後進入下一個新的時間週期

簡單實現

有了上面的瞭解,就可以去實現簡單debounce和throttle了。

debounce實現

首先來看看debounce的實現,根據前面對debounce的描述:

  1. debounce函式會通過閉包維護一個timer
  2. 當同一action在delay的時間間隔內再次觸發,則清理timer,然後重新設定timer

可以在Chrome中執行下面的程式碼,看看debounce的效果,程式碼Github連結

var debounce = function(action, delay) {
    var timer = null;
    
    return function() {
        var self = this, 
              args = arguments;
              
        clearTimeout(timer);
        timer = setTimeout(function() {
            action.apply(self, args)
        }, delay);
    }
}

// example
function resizeHandler() {
    console.log("resize");
}

window.onresize = debounce(resizeHandler, 300);
  
  

throttle實現

throttle跟debounce的最大不同就是,throttle會有一個閥值,當到達閥值的時候action必定會執行一次。

所以throttle的實現可以基於前面的debounce的實現,只需要加上一個閥值,程式碼Github連結

var throttleV1 = function(action, delay, mustRunDelay) {
    var timer = null,
          startTime;
          
    return function() {
        var self = this, 
              args = arguments, 
              currTime = new Date();
              
        clearTimeout(timer);
        
        if(!startTime) {
            startTime = currTime;
        }
        
        if(currTime - startTime >= mustRunDelay) {
            action.apply(self, args);
            startTime = currTime;
        }
        else {
            timer = setTimeout(function() {
                action.apply(self, args);
            }, delay);
        }
    };
};

其實,對於上面的實現可以進心簡化,只是通過閉包維護一個開始的時間:

var throttleV2 = function(action, delay){
    var statTime = 0;
    
    return function() {
        var currTime = +new Date();
        
        if (currTime - statTime > delay) {
            action.apply(this, arguments);
            statTime = currTime ;
        }
    }
}    

// example
function resizeHandler() {
    console.log("resize");
}

window.onresize = throttleV2(resizeHandler, 300);

總結

通過前面的介紹,應該對debounce和throttle有一個直觀的認識了:

  • debounce:把觸發非常頻繁的事件合併成一次執行
  • throttle:設定一個閥值,在閥值內,把觸發的事件合併成一次執行;當到達閥值,必定執行一次事件

瞭解了throttle和debounce之後,下面看看他們的常用場景:

debounce

  • 對於鍵盤事件,當用戶輸入比較頻繁的時候,可以通過debounce合併鍵盤事件處理
  • 對於ajax請求的情況,例如當頁面下拉超過一定返回就通過ajax請求新的頁面內容,這時候可以通過debounce合併ajax請求事件

throttle

  • 對於鍵盤事件,當用戶輸入非常頻繁,但是我們又必須要在一定時間內(閥值)內執行處理函式的時候,就可以使用throttle

    • 例如,一些網頁遊戲的鍵盤事件
  • 對於滑鼠移動和視窗滾動,滑鼠的移動和視窗的滾動會帶來大量的事件,但是在一段時間內又必須看到頁面的效果

    • 例如對於可以拖動的div,如果使用debounce,那麼div會在拖動停止後一下子跳到目標位置;這時就需要使用throttle