搞懂debounce(防抖)和throttle(節(jié)流)

這篇文章講概念、應用場景和實現(xiàn)思路,源碼在最后面。

性能和速度是程序的敵人,以致于每一個優(yōu)秀的程序員都在孜孜不倦的提升軟件的性能和速度,從而提升產品的用戶體驗。

下面介紹的是debounce和throttle,這兩種技術能夠改善程序的性能,它們非常相似但是不同的技術。

當dom事件被頻繁觸發(fā)時,使用debounce或throttle就非常有用了,因為它能夠在事件和函數執(zhí)行之間添加一層控制。

這里推薦使用Lodash工具庫,引入后直接使用_.debounce_.throttle,非常簡單和方便,當然前提是需要理解它們的作用。

debounce防抖動

debounce允許我們把一組調用壓縮為單個調用。

想象一下你在電梯里面,門準備關上,突然,有人闖進來,這時電梯不會開始移動,而是門重新打開,然后等待一會。如果還有人進來,它會重復這一過程,直到沒有人再進來,最后門自動關上,電梯開始在樓層間移動。這是為什么?因為工程師們也對電梯進行了資源優(yōu)化,用更少的資源完成任務,充分發(fā)揮電梯的效用。這個電梯的例子跟debounce的應用場景非常相似。

應用場景:

  1. 調整resize。
    當監(jiān)聽了窗口的resize事件時,并且調整瀏覽器窗口大小的時候,會觸發(fā)大量的resize事件,所以呢當你拖拽的時候,調用處理函數做大量計算的時候,會發(fā)現(xiàn)拖拽的過程有點卡頓,掉幀。

這時我們可以使用debounce,因為我們關注的是最終值,也就是我們最后停止拖動瀏覽器窗口的值。

  1. input的輸入和發(fā)送請求。
    在input里輸入文字然后發(fā)送網絡請求,有一種較優(yōu)的方案就是期望用戶輸入完畢然后再發(fā)送網絡請求,而不是輸入的過程中不斷的發(fā)出請求,這能有效的優(yōu)化網絡服務。

throttle節(jié)流

throttle 只允許在每x毫秒內執(zhí)行一次操作。

它跟debounce的不同主要是:throttle的執(zhí)行是有規(guī)律的,會每x毫秒內執(zhí)行一次。

使用場景:
一個相當普遍的例子,用戶在使用一個無限滾動的頁面,你需要檢測用戶的位置到底部之間的距離,如果用戶接近屏幕底部,我們應該發(fā)送Ajax請求更多內容然后添加到頁面上。實現(xiàn)這一功能需要監(jiān)聽頁面的scroll事件,然而在手機端緩慢的滾動頁面會觸發(fā)上百次事件,這時候有了throttle就可以對其進行優(yōu)化,比如每250ms內只調用一次,這樣用戶基本感覺不到有任何體驗上的差別,也優(yōu)化了程序的性能。

在這里我們心愛的debounce是不適合的,因為debounce只在用戶停止?jié)L動時才觸發(fā)(調用)函數。而我們需要的是在用戶在滾動頁面過程中快要到達底部的時候獲取更多內容,使用throttle才能持續(xù)的進行檢測用戶位置到底部之間的距離。

requestAnimationFrame (rAF)

rAF 是對函數的執(zhí)行進行限速的額外的一種方式。大致等同于_.throttle(dosomething, 16)。但它可以是throttle的替代者,它更流暢和平滑,它是瀏覽器的標準API。

根據經驗,我使用rAF函數的一些場景主要是繪畫或動畫,重新計算元素的位置等。

觸發(fā)網絡請求或決定是否添加/移除一個class(觸發(fā)一個CSS動畫),我會考慮_.debounce or _.throttle,使用它們你能更靈活的控制執(zhí)行速率(用200ms替換16ms)。

在underscore或lodash這兩個框架里面都沒有提供或實現(xiàn)rAF,因為使用它非常的簡單。

例子:

var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

function step(timestamp) {
  if (!start) start = timestamp;
  var progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 2000) {
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);

總結:
使用debounce, throttle and requestAnimationFrame來優(yōu)化你的事件操作。三者的技術和思路都略微不同,但它們都是非常有用以及彼此互補。

  • debounce:debounce允許我們把一組事件調用壓縮為單個調用。
  • throttle:每x毫秒執(zhí)行一次。就像每隔200毫秒檢查一次滾動位置。
  • requestAnimationFrame:可以是throttle的替代者,當你的函數為重新計算和渲染元素到屏幕上,而且你想得到平滑的過渡動畫。注意:不支持IE9。

Github:
throttle 和 debounce函數代碼

實現(xiàn)思路:

  • debounce:創(chuàng)建一個指定x毫秒的setTimeout(fn,x),如果在該setTimeout(fn,x)的x毫秒內再次產生新的事件,就會先去取消該setTimeout,然后重新創(chuàng)建一個指定x毫秒的setTimeout(fn,x),不斷重復這一過程,直到沒有再產生新的事件,x毫秒后調用函數fn。
  • throttle:setTimeout(fn,x - elapsed)。節(jié)流的實現(xiàn)要多兩個額外的變量,一個是記錄上一次函數fn執(zhí)行的時間(lastExec),一個是記錄流逝的時間(elapsed) = 當前setTimeout創(chuàng)建的時間 - 上一次函數fn執(zhí)行的時間(lastExec),每產生一個新的事件(創(chuàng)建新的setTimeout),都會先去取消上一個setTimeout,所以它的定時器要這么寫setTimeout(fn,x - elapsed)就能每x毫秒執(zhí)行一次函數fn了,每一次函數fn被執(zhí)行都會更新lastExec的值為當前時間。

參考資料:
https://css-tricks.com/debouncing-throttling-explained-examples/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容