實例解析防抖動(Debouncing)和節流閥(Throttling)

psychicread180 8年前發布 | 12K 次閱讀 Pen Ajax 前端技術

防抖( Debounce )和節流( throttle )都是用來控制某個函數在一定時間內執行多少次的技巧,兩者相似而又不同。

當我們給 DOM 綁定事件的時候,加了防抖和節流的函數變得特別有用。為什么呢?因為我們在事件和函數執行之間加了一個控制層。記住,我們是無法控制 DOM 事件觸發頻率的。

看下滾動事件的例子:

See the Pen Scroll events counter by Corbacho ( @dcorb ) on CodePen .

當使用觸控板,滾動滾輪,或者拖拽滾動條的時候,一秒可以輕松觸發30次事件。經我的測試,在智能手機上,慢慢滾動一下,一秒可以觸發事件100次之多。這么高的執行頻率,你的滾動回調函數壓力大嗎?

早在2011年,推ter 網站拋出了一個問題:向下滾動 推ter 信息流的時候,變得很慢,很遲鈍。John Resig 發表了 一篇博客解釋這個問題 ,文中解釋到直接給 scroll 事件關聯昂貴的函數,是多么糟糕的主意。

John(5年前)建議的解決方案是,在 onScroll 事件外部,每 250ms 循環執行一次。簡單的技巧,避免了影響用戶體驗。

現如今,有一些稍微高端的方式處理事件。我來結合用例介紹下 Debounce,Throttle 和 requestAnimationFrame 吧。

防抖動(Debounce)

防抖技術可以把多個順序地調用合并成一次。

假想一下,你在電梯中,門快要關了,突然有人準備上來。電梯并沒有改變樓層,而是再次打開梯門。電梯延遲了改變樓層的功能,但是優化了資源。

在頂部按鈕上點擊或移動鼠標試一下:

See the Pen Debounce. Trailing by Corbacho ( @dcorb ) on CodePen .

你可以看到連續快速的事件是如何被一個 debounce 事件替代的。但是如果事件觸發的時間間隔過長,debounce 則不會生效。

前緣(或者“immediate”)

你會發現,直到事件停止快速執行以后,debounce 事件才會觸發相應功能。為何不立即觸發呢?那樣的話就跟原本的非 debounce 處理無異了。

直到兩次快速調用之間的停頓結束,事件才會再次觸發。

這是帶 leading 標記的例子:

前緣 debounce 的例子

在 underscore.js 中,選項叫 immediate ,而不是 leading :

See the Pen Debounce. Leading by Corbacho ( @dcorb ) on CodePen .

Debounce 實現

我首次看到 debounce 的 JavaScript 實現是在 2009 年的 John Hann 的博文

不久后,Ben Alman 做了個 jQuery 插件 (不再維護),一年后 Jeremy Ashkenas 把它 加入了 underscore.js 。而后加入了 Lodash 。

See the Pen New example by Corbacho ( @dcorb ) on CodePen .

Lodash 給 _.debounce 和 _.throttle 添加了 不少特性 。之前的 immediate 被 leading (最前面) 和 trailing (最后面) 選項取代。你可以選一種,或者都選,默認只有 trailing 啟用。

新的 maxWait 選項(僅 Lodash 有)本文未提及,但是也很有用。事實上,throttle 方法是用 _.debounce 加 maxWait 實現的,你可以看 lodash 源碼

Debounce 實例

調整大小的例子

調整桌面瀏覽器窗口大小的時候,會觸發很多次 resize 事件。

看下面 demo:

See the Pen Debounce Resize Event Example by Corbacho ( @dcorb ) on CodePen .

如你所見,我們為 resize 事件使用了默認的 trailing 選項,因為我們只關心用戶停止調整大小后的最終值。

基于 AJAX 請求的自動完成功能,通過 keypress 觸發

為什么用戶還在輸入的時候,每隔50ms就向服務器發送一次 AJAX 請求? _.debounce 可以幫忙,當用戶停止輸入的時候,再發送請求。

此處也不需要 leading 標記,我們想等最后一個字符輸完。

See the Pen Debouncing keystrokes Example by Corbacho ( @dcorb ) on CodePen .

相似的使用場景還有,直到用戶輸完,才驗證輸入的正確性,顯示錯誤信息。

如何使用 debounce 和 throttle 以及常見的坑

自己造一個 debounce / throttle 的輪子看起來多么誘人,或者隨便找個博文復制過來。 我是建議直接使用 underscore 或 Lodash 。如果僅需要 _.debounce 和 _.throttle 方法,可以使用 Lodash 的自定義構建工具,生成一個 2KB 的壓縮庫。使用以下的簡單命令即可:

npm i -g lodash-cli
lodash-cli include=debounce,throttle

常見的坑是,不止一次地調用 _.debounce 方法:

// 錯誤
$(window).on('scroll', function() {
   _.debounce(doSomething, 300); 
});

// 正確
$(window).on('scroll', _.debounce(doSomething, 200));

debounce 方法保存到一個變量以后,就可以用它的私有方法 debounced_version.cancel() ,lodash 和 underscore.js 都有效。

var debounced_version = _.debounce(doSomething, 200);
$(window).on('scroll', debounced_version);

// 如果需要的話
debounced_version.cancel();

Throttle(節流閥)

使用 _.throttle 的時候,只允許一個函數在 X 毫秒內執行一次。

跟 debounce 主要的不同在于,throttle 保證 X 毫秒內至少執行一次。

節流閥實例

無限滾動

用戶向下滾動無限滾動頁面,需要檢查滾動位置距底部多遠,如果鄰近底部了,我們可以發 AJAX 請求獲取更多的數據插入到頁面中。

我們心愛的 _.debounce 就不適用了,只有當用戶停止滾動的時候它才會觸發。只要用戶滾動至鄰近底部時,我們就想獲取內容。

使用 _.throttle 可以保證我們不斷檢查距離底部有多遠。

See the Pen Infinite scrolling throttled by Corbacho ( @dcorb ) on CodePen .

requestAnimationFrame(rAF)

requestAnimationFrame 是另一種限速執行的方式。

跟 _.throttle(dosomething, 16) 等價。它是高保真的,如果追求更好的精確度的話,可以用瀏覽器原生的 API 。

可以使用 rAF API 替換 throttle 方法,考慮一下優缺點:

優點

  • 動畫保持 60fps(每一幀 16 ms),瀏覽器內部決定渲染的最佳時機
  • 簡潔標準的 API,后期維護成本低

缺點

  • 動畫的開始/取消需要開發者自己控制,不像 ‘.debounce’ 或 ‘.throttle’由函數內部處理。
  • 瀏覽器標簽未激活時,一切都不會執行。
  • 盡管所有的現代瀏覽器 都支持 rAF ,IE9,Opera Mini 和 老的 Android 還是 需要打補丁
  • Node.js 不支持,無法在服務器端用于文件系統事件。

根據經驗,如果 JavaScript 方法需要繪制或者直接改變屬性,我會選擇 requestAnimationFrame ,只要涉及到重新計算元素位置,就可以使用它。

涉及到 AJAX 請求,添加/移除 class (可以觸發 CSS 動畫),我會選擇 _.debounce 或者 _.throttle ,可以設置更低的執行頻率(例子中的200ms 換成16ms)。

rAF 實例

靈感來自于 Paul Lewis 的文章 ,我將用 requestAnimationFrame 控制 scroll 。

16ms 的 _.throttle 拿來做對比,性能相仿,用于更復雜的場景時,rAF 可能效果更佳。

See the Pen Scroll comparison requestAnimationFrame vs throttle by Corbacho ( @dcorb ) on CodePen .

 

來自: http://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/

 

 本文由用戶 psychicread180 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!