throttle與debounce的區別

前幾天看到一篇文章,個人公衆號裏也分享了《一次發現underscore源碼bug的經歷以及對學術界拿來主義的思考》具體文章詳見,微信公衆號:
圖片描述
文中講了你們對throttle和debounce存在誤解,同時提到了《高程3》中實現節流方法存在一些問題,爲了更好的理解這兩個概念,搜了不少相關文章,詳見文章底部。javascript

throttle與debounce是兩個相似的概念,目的都是隨着時間的推移控制執行函數的次數,可是有些細微的差異。css

當咱們爲DOM事件關聯方法時,若咱們有一個debounced和throttled函數將會很方便,爲什麼?由於這樣咱們能夠在事件和執行函數之間添加一層控制,注意咱們並無去控制DOM事件觸發的次數。html

例如,咱們談一下scroll事件,看下面的例子:html5

<p data-height="268" data-theme-id="0" data-slug-hash="xVpoOe" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Scroll events counter by ghostcode (@ghostcode) on CodePen.</p>java

當你在觸控板或者鼠標滾動時,每次最少會達到30次,在手機上更多。但是你的滾動事件處理函數對這個頻率是否應付的過來?node

在2011年,Twitter網站曾爆出一個問題:當你在主頁往下滾動時,頁面會變得緩慢以至沒有響應。John Resig發表了一篇文章《 a blog post about the problem》指出直接在scroll事件上面綁定高消耗的事件是一個多麼愚蠢的想法。jquery

在那個時候John建議使用一個獨立於scroll事件且每250ms執行的輪詢方法。這樣的話處理方法就不會耦合於事件。經過這個簡單的技術,咱們能夠提升用戶體驗。git

如今有一些更先進的事件處理方法,讓我來給你介紹:__Debounce,Throttle和requestAnimationFrame__,同時會介紹一些適用的場景。github

Debouncenpm

Debounce技術使咱們能夠將一個連續的調用歸爲一個。

圖片描述

想象你在電梯的場景,當電梯門開始要關閉的時候,忽然一我的進來,此時電梯並不會關閉而且也不會執行改變樓層的方法,若是還有人進來一樣的事情會發生:電梯延遲執行它的方法(改變樓層),優化了它的資源。

本身嘗試一下,在按鈕上點擊或者移動鼠標:

<p data-height="268" data-theme-id="0" data-slug-hash="vGpqLO" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce. Trailing by ghostcode (@ghostcode) on CodePen.</p>

你能夠看到快速連續的事件是如何經過一個debounce事件來表示的。

Leading edge (or "immediate")

你能夠發現事件結束的時候,debounce的事件並無當即執行而是等待了一些時間才觸發。爲什麼不當即觸發,就像開始沒有使用debounce事件處理?直到在連續執行的事件中有一個暫停,纔會再次觸發。

你能夠經過一個__leading__的參數作到:

圖片描述

在underscore.js中,這個參數叫immediate。

本身嘗試一下:

<p data-height="268" data-theme-id="0" data-slug-hash="VaQwRm" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce. Leading by ghostcode (@ghostcode) on CodePen.</p>

Debounce Implementations

2009年在John Hann的文章中第一次看到debounce的實現方法。

在那以後不久,Ben Alman寫了一個jQuery插件(如今不在維護),一年之後Jeremy Ashkenas把此方法添加到underscore.js中,不久又被添加到lodash中。

<p data-height="268" data-theme-id="0" data-slug-hash="GZQRLv" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen debounce-click by ghostcode (@ghostcode) on CodePen.</p>

這三種實現方法內部不一樣,可是接口幾乎一致。

有段時間underscore採用了Lodash的實現方法,可是在我發現了一個bug以後,自此兩個庫的實現開始分道揚鑣。

Lodash在_.debounce和_.throttle中添加了許多特性。immediate標示替代了leading和trailing。你能夠二選一或者都選,默認狀況下,只有trailing是開啓的。

Debounce Examples

當改變瀏覽器窗口時,resize事件會觸發屢次。

<p data-height="268" data-theme-id="0" data-slug-hash="PNQorE" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce Resize Event Example by ghostcode (@ghostcode) on CodePen.</p>

如你所見,咱們使用了__trailing__參數,由於咱們只對用戶中止改變瀏覽器大小時最後一次事件感興趣。

AutoComplete中的Ajax請求使用的keypress

當用戶仍舊在輸入的時候,爲什麼每隔50ms發送Ajax請求?__ _.debounce __能夠幫助咱們避免額外的工做,只在用戶中止輸入的時候發送請求。

<p data-height="268" data-theme-id="0" data-slug-hash="wGyvVj" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debouncing keystrokes Example by ghostcode (@ghostcode) on CodePen.</p>

另外一個使用場景是在進行input校驗的時候,「你的密碼過短」等相似的信息。

如何使用debounce和throttle以及常見的陷阱?

能夠本身實現這兩個方法或者隨便複製別人blog中的實現方法,個人建議是直接使用underscore和lodash中的方法。若是你只須要這兩個方法,能夠定製輸出lodash方法:

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

一個常見的陷阱:

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

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

debounce方法賦值給一個變量以後容許咱們調用一個私有方法:__debounced_version.cancel()__:

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

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

Throttle

使用__ _.throttle __,咱們不容許方法在每Xms間執行超過一次。

和debounce的主要區別是throttle保證方法每Xms有規律的執行。

Throttling Examples

一個至關常見的例子,用戶在你無限滾動的頁面上向下拖動,你須要判斷如今距離頁面底部多少。若是用戶快接近底部時,咱們應該發送請求來加載更多內容到頁面。

在此__ _.debounce 沒有用,由於它只會在用戶中止滾動時觸發,但咱們須要用戶快到達底部時去請求。經過 _.throttle __咱們能夠不間斷的監測距離底部多遠。

<p data-height="268" data-theme-id="0" data-slug-hash="xVYbGZ" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Infinite scrolling throttled by ghostcode (@ghostcode) on CodePen.</p>

requestAnimationFrame (rAF)

requestAnimationFrame是另外一個頻率限制的方法。

它能夠經過__ _.throttle(dosomething, 16)__實現,但爲了更加精準瀏覽器提供了內置API。

咱們可使用rAF API做爲throttle方法的替代,考慮一下利弊:

利:

  • 目標60fps(16ms每貞),可是內部使用最優的時間間隔來渲染
  • 使用簡單而且是標準API,之後不會變更,不須要維護

弊:

  • rAF的開始或者取消須要咱們本身處理,不像.debounce和.throttle內部實現
  • 瀏覽器Tag沒有激活,它就不會執行
  • 即便多數現代瀏覽器支持,可是IE9,Opera Mini以及老版本Android依舊不支持。A polyfill到如今依舊須要
  • rAF在node.js中不支持

根據經驗,我建議在JS執行"painting"或"animating"中直接操做屬性和從新計算元素位置時使用rAF。

發送Ajax請求或者是否添加/刪除class(觸發一個CSS動畫)時,我會考慮debounce和throttle,此時你能夠下降執行頻率(200ms而不是16ms)。

rAF的例子

Paul Lewis的文章激發下,我只在scroll事件中提供例子。

我一步步的調throttle到16ms,但願給一個相似的體驗,可是rAF在複雜場景下或許會提供更好的結果。

<p data-height="268" data-theme-id="0" data-slug-hash="qZxEaq" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Scroll comparison requestAnimationFrame vs throttle by ghostcode (@ghostcode) on CodePen.</p>

一個更好的例子我是在headroom.js中看到的,這裏經過一個對象封裝,進行了邏輯解藕

總結:
使用debounce,throttle和requestAnimationFrame優化你的事件處理函數。每個方法有一些細微的差異,三個都頗有用並且互相彌補。

  • __debounce:__把忽然涌進的事件(鍵盤事件)歸位一個
  • __throttle:__保證持續執行方法分隔爲每Xms執行一次。就像每200ms監測滾動位置來觸發css動畫。
  • __requestAnimationFrame:__throttle的替代方案,當你的方法須要從新計算和渲染元素同時你須要更平滑的變更或動畫。注意:IE9- 不支持。
  1. https://blog.coding.net/blog/...
  2. https://css-tricks.com/the-di...
  3. http://stackoverflow.com/ques...
  4. http://demo.nimius.net/deboun...
相關文章
相關標籤/搜索