經過例子解釋去抖和節流函數

本文翻譯自Debouncing and Throttling Explained Through Examplesjavascript

下面這篇文章是David Corbacho一位倫敦的前端開發工程師寫的。咱們以前談到過這個話題,可是此次,David將舉出各類生動的例子來讓概念更加容易理解。css

去抖節流是兩種相似的(可是不一樣!)的技術,它們用來控制隨着時間變化咱們容許一個函數被執行的次數。html

當咱們將一個函數綁定做爲DOM事件的處理器時使用去抖或者節流版本的函數尤爲管用。爲何?由於這樣作咱們就給本身在事件與函數執行之間添加了一個控制層。記住,咱們不會去控制DOM事件即將會被觸發多少次,事件觸發的次數是會變化的。前端

舉個例子,讓咱們來看看滾動事件。看下面的例子:html5

Scroll events counterjava

當咱們使用觸控板,滾輪或者用鼠標拖動滾動條的時候會輕易地每秒觸發30次事件。可是在智能手機上緩慢地滾動頁面會很容易的觸發100次事件每秒。請問你的事件處理器準備好了這樣的觸發頻率嗎?node

在2011年,推特網上出現了這樣一個問題:當你向下滾動你的簡訊頁面的時候,頁面變得緩慢並且沒法響應。John Resig發佈了一篇文章a blog post about the problem來描述將作了不少複雜操做的函數綁定到scroll事件上是一個多麼壞的主意。jquery

John建議的解決辦法(在那個時候,五年前)是在scroll事件以外循環運行函數,每過250ms執行一次。這樣的話事件處理函數就不會被同時觸發運行。經過這個簡單的方法,就能夠避免用戶執行貴重的操做。webpack

這些天來處理事件的複雜精妙的方法變得更多了。讓我來向你介紹去抖,節流和requestAnimationFrame。咱們還會看與它們相匹配的使用場景。git

Debounce

去抖技術容許咱們將屢次相繼觸發的函數調用集合成單一的一次調用。

想象你在一個電梯中。電梯門開始閉合,而後忽然間另一我的想要進入電梯。電梯不會開始執行改變樓層的函數,而是從新打開電梯門。如今另外一我的出現了又會從新打開門。電梯雖然會延遲它的函數(變換樓層)執行,可是這樣優化了資源。

本身試一試。在按鈕上方點擊或者移動指針。

Debounce. Trailing

從上面的例子能夠看到一個單個去抖事件下相繼快速發生的事件觸發是如何表現的。可是若是事件觸發之間有很大的時間間隔,去抖就不會發生。

Leading edge (or "immediate")前邊緣(或者說當即)

你也許會以爲惱怒,由於去抖的事件會在觸發函數執行以前一直等待,直到事件中止的時候忽然觸發函數。爲何不當即觸發函數執行,這樣就會表現地像沒有去抖的原始事件處理函數同樣?可是不須要在忽然執行函數的停頓的時候再次觸發函數。

你能夠這樣作!下面是在等待以前就觸發的例子:

在underscore庫中,這個配置項叫immediate而不是leading

下面的例子本身試一試:

Debounce. Leading

去抖函數的實現

我第一次看到去抖函數的javascript實現是在2009年在John Hann的這篇博客文章(也是他發明了這個術語)。

以後,Ben Alman創造了一個jquery插件(已經再也不維護),一年以後,Jeremy Ashkenas將去抖函數加入到了underscore.js中。最後lodash中也加入了去抖函數。

三種實現的內部代碼都有一點區別,可是表面上使用起來都是一致的。

曾有一段時間underscore採用了lodash的去抖和節流實現,在2013年我在_.debounce方法之中發現了一個bug以後。自從那次以後,兩個庫的實現都變得不同了。

lodash庫爲_.debounce和_.throttle函數添加了更多的特性。原來的immediate標識被替換成了leading和trailing選項。你能夠選擇其中之一或者兩個都選。默認狀況下只有trailing選項是開啓的。

新的maxWait選項(只有lodash庫有這個參數)不在本文討論範圍內可是這個參數仍是頗有用的。實際上,節流函數的定義就是_.debounce和maxWait組合的結果,你能夠去看看lodash源碼。

去抖函數例子

resize

當改變桌面瀏覽器窗口的大小的時候,瀏覽器會觸發不少次resize事件當用戶拖動窗口大小的時候。

看看下面這個demo:

Debounce Resize Event Example

正如你所見,咱們對於resize事件使用默認的trailing配置,由於咱們只對於最後的窗口大小值感興趣,當用戶中止改變窗口大小的時候。

鼠標按下事件自動完成表單而後發送ajax請求

當用戶仍然在輸入的時候,爲何每過50毫秒就要發送請求到服務器?_.debounce能夠幫助咱們避免額外的工做,只有在用戶中止打字的時候才發送請求。

在這裏使用leading標識是沒有意義的,咱們想要等待到用戶輸入最後一個字母的時候。

Debouncing keystrokes Example

一個相似的使用場景就是等待直到用戶中止輸入而後驗證用戶的輸入,而後提示「密碼過短」之類的提示信息。

去抖和節流如何使用?以及容易犯的錯誤

建立你本身實現的去抖和節流函數是頗有吸引力的,或者從網上找一些別人的博文裏的實現。個人建議是直接使用underscore和lodash庫。若是你只須要庫中的_.debounce和_.throttle函數,你可使用lodash自定義構建器來輸出一個自定義的縮小的庫。使用下面的命令來建立:

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

也就是說,最多見的狀況就是使用lodash的throttle和debounce模塊或者直接引入單獨的模塊到webpack,browserify,rollup中打包。

一個容易犯的錯誤就是調用_.debounce函數超過一次:

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

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

爲去抖函數建立一個變量能夠容許咱們調用去抖的私有方法debounced_version.cancel(),lodash和underscore均可用,也許你會須要這樣的功能。

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

// If you need it
debounced_version.cancel();

Throttle

經過使用_.throttle,咱們不容許函數在單位時間X毫秒內執行超過一次。

節流和去抖的主要區別就是節流函數會保證了函數的執行是有規律的,至少每X毫秒只能執行一次。

和去抖函數同樣,節流技術可使用Ben的插件,或者underscore或者lodash。

節流函數例子

無限滾動

這是一個比較廣泛的例子。用戶向下滾動無限滾動的頁面。你須要判斷當前滾動的位置和頁面底部的距離。若是用戶的位置靠近頁面底部了,咱們應該憑藉ajax請求更多的頁面內容而後將內容插入頁面結尾。

這裏_.bounce函數就不能幫上忙了。由於它只能在用戶中止滾動的時候觸發請求。而咱們須要在用戶尚未抵達頁面底部的時候就開始獲取新的內容。

經過_.throttle咱們能夠保證持續檢查當前的位置距離頁面底部的距離。

Infinite scrolling throttled

requestAnimationFrame (rAF)

requestAnimationFrame是另一種方式來限制函數調用的頻率。

它能夠被認爲是_.throttle(dosomething, 16)。可是它擁有更高的保真度,由於它是瀏覽器原生API以更好的精確性爲目標。

咱們可使用rAF API做爲一個可選方案來使函數節流,思考下面舉出的優缺點:

優勢

  • 目標爲60fps(每16ms一幀)可是內部程序會決定最佳時間如何去安排頁面渲染
  • 簡單並且標準的API,將來不會改變。更少的兼容性問題

缺點

  • 何時啓動和結束rAF方法是咱們的責任,而不像debounce或者throttle方法那樣在內部被管理。
  • 若是瀏覽器tab頁沒有被激活,那麼它就不會執行。雖然對於滾動,鼠標和鍵盤事件來講這一點無所謂。
  • 雖然全部現代瀏覽器支持rAF方法,可是IE9,Opera Mini還有舊的安卓手機依然不支持。現在仍然須要一個polyfill。
  • nodejs中不支持rAF,因此你沒法在服務端使用它來對文件系統的事件函數節流。

單憑經驗來講,我會使用requestAnimationFrame方法若是你的js函數是做爲繪製頁面元素或者動畫的用途,若是牽扯了從新計算元素位置那麼就應該使用它來控制。

當發送ajax請求,或者決定是否添加或移除一個css class(這樣會觸發一個css動畫),我會考慮使用去抖或者節流方法,這樣你能夠設置更低的執行比率(200ms執行一次而不是16ms)。

若是你認爲rAF方法應該在underscore或lodash庫中被實現,可是這兩個庫都拒絕實現rAF,由於它屬於一種特殊使用場景,並且原生的API已經足夠簡單來直接調用。

rAF例子

我只會在這個例子上演示rAF方法如何控制滾動事件的動畫,受到Paul Lewis的文章的啓發,他在文章中一步一步解釋了這個例子的邏輯。

我將rAF控制的動畫和_.throttle節流函數(每16毫秒)控制的動畫並排放在一塊兒來直觀比較。最後兩個函數的表現差很少同樣,可是也許rAF方法會在更加複雜的場景中有更好的表現。

Scroll comparison requestAnimationFrame vs throttle

關於rAF技術的更加高級的例子我曾在headroom.js這個庫中看到過,其實現的邏輯減弱了並且將操做方法包裹在一個對象中。

Conclusion

使用去抖,節流或者requestAnimationFrame去優化你的事件處理函數。每一種技術都有一點差別,可是這三種方法都頗有用而且它們互相補充。

總結:

去抖函數:將忽然連續觸發的事件(例如頻繁按下鍵盤按鍵)集合成單獨的一次觸發。

節流函數:保證每單位時間X毫秒內函數的執行是流暢的。例如每200毫秒就檢查一次滾動位置來觸發css動畫。

requestAnimationFrame函數:一個可選的節流方法。當你的函數在頁面上從新計算和渲染元素的時候,或者你想要保證平滑的動畫,那麼就用它。注意:IE9瀏覽器不支持這個方法。

相關文章
相關標籤/搜索