debounce(防抖) & throttle(節流)

還好還好哈哈22發表於2018-09-27

一、前言

想必前端工程師看到這兩個單詞都是十分的熟悉。這兩種技術,都是為了避免短時間內大量觸發同一種操作所做的努力。特別是,當這一操作代價比較大,如涉及到DOM操作或瀏覽器迴流等行為時,會造成極大的效能損耗,嚴重時甚至會導致瀏覽器的崩潰。

二、一些錯誤的認識

debounce(防抖)和throttle(節流),這兩種方法的原理很類似。都是利用setTimeout和閉包控制事件處理程式的觸發頻率。然而兩種方法的具體實現方法及最終效果又有細微的不同。正因為兩種方法是如此的類似,以及網上資料的混亂,加重了初學者的學習成本。

function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}
複製程式碼

對於同一段程式碼,這三處地方卻給出了完全不同的解釋:AlloyTeam團隊和javascript高階語言程式設計都將其標註為throttle,而包括第三篇blog在內的幾乎所有其他地方都將其標註為debounce。我陷入了疑惑。本著前兩者的權威性,一開始我也認為這是防抖,但是,直到我找到了這個:

其中明確指出“Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called”。因此,上述程式碼應該是debounce(防抖)。 而相應的,throttle(節流)對應的描述則是“Throttling enforces a maximum number of times a function can be called over time”。

三、debounce(防抖)

廢話不多說,先上段程式碼

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


div.onclick = debounce(() => { console.log('點選事件') }, 1000);
複製程式碼

debounce的效果是:當事件被觸發時,事件處理函式不會立即執行,而是會被放到setTimeout中延時delay執行;而當delay時間內再次觸發事件,則前一個setTimeout生成的ID會被clearTimeout清除,同時生成新的ID。這樣,只有在delay時間段內,沒有新的event,事件處理函式才會真正執行。

這一方法可以用在輸入框中,若是每次input事件觸發時,都傳送一個ajax操作,則會造成極大的效能浪費。使用debounce,則只會在使用者停止輸入後才會傳送ajax請求。

但是這一方式也有自己的問題:如果事件一直源源不斷的觸發,則可能導致在很長一段時間內,事件處理程式都不會被呼叫。在某些場景下,這可能會導致使用者體驗不佳。比如說,需要為dialog彈窗增加滑鼠拖動的功能,如果使用debounce的方法,則可能會導致在滑鼠移動期間,彈窗一直不動,直到滑鼠停止移動,彈窗會一下子跳到終止位置。這顯然是不合理的。

四、throttle(節流)

function throttle(fun, delay) {
  let last, deferTimer
  return function (args) {
      let that = this
      let _args = arguments
      let now = +new Date()
      if (last && now < last + delay) {
          clearTimeout(deferTimer)
          deferTimer = setTimeout(function () {
              last = now
              fun.apply(that, _args)
          }, delay)
      }else {
          last = now
          fun.apply(that,_args)
      }
  }
}

let throttleAjax = throttle(ajax, 1000)

let inputc = document.getElementById('throttle')
inputc.addEventListener('keyup', function(e) {
  throttleAjax(e.target.value)
})
複製程式碼

throttle的的效果和debounce類似,也是會限制delay時間內事件的觸發次數。但是,相較於debounce,throttle的好處在於當事件連續觸發時,每delay時間段,事件處理函式都會執行一次。從而避免了debounce的連續觸發事件,導致事件處理函式一直無法呼叫的極端情況。

throttle可以用在滾屏預載入,彈窗拖動等場景。這些場景的特徵是,在事件節流的基礎上,要求每隔一段時間,至少執行一次事件處理函式。

五、總結

debounce解決了短時間內大量事件觸發的問題,只有當delay時間段內,沒有新的事件觸發,才會真正執行事件處理函式;但是,極端情況下,事件處理函式可能一直得不到執行。

throttle則在解決事件節流的基礎上,定期執行事件處理函式。

ps:以上純屬個人理解,如有錯誤,歡迎大家指出

相關文章