debounce與throttle區別
在2011年,Twitter網站曾爆出一個問題:在主頁往下滾動時,頁面會變得緩慢以致沒有響應。John Resig發表了一篇文章《 a blog post about the problem》指出直接在scroll事件上面繫結高消耗的事件是一個多麼愚蠢的想法。現在專案中大家都會對類似的scroll或者resize事件都進行了節流控制,下述是我們經常用到,也是《JavaScript高階程式設計》- JavaScript高階技巧中提及的節流方式。
/** * 節流函式(JavaScript高階程式設計) * @param method 方法 * @param scope 當前函式執行作用域 */ function throttle(method, scope) { clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(scope); }, 100); } function resizeDiv(){ var div = document.getElementById("myDiv"); div.style.height = div.offsetWidth + "px"; } // 節流在resize事件中最常用 window.onresize = function(){ throttle(resizeDiv); };
之前一直覺得上述程式碼就是實現了真正的節流,也沒去深入研究。直到最近在和之前的同事討論圖表的問題,說起了“throttle和debounce”,他說我們專案中使用的不是真正意義上的throttle,而是一個debounce的簡單實現。這裡先簡單介紹一下“throttle和debounce”,二者都是隨著時間推移控制執行函式的次數來達到較少資源消耗,特別在事件觸發上,尤為重要。
-
debounce(func, wait, immediate):建立並返回函式的防反跳版本,將延遲函式的執行(真正的執行)在函式最後一次呼叫時刻的wait毫秒之後,對於必須在一些輸入(多是一些使用者操作)停止之後再執行的行為有幫助。將一個連續的呼叫歸為一個!
-
throttle(func, wait, options):建立並返回一個像節流閥一樣的函式,當重複呼叫函式的時候,最多每隔指定的wait毫秒呼叫一次該函式; 不允許方法在每wait ms間執行超過一次!
舉個例子:頁面存在一個按鈕,通過throttle和debounce包括其監聽函式,wait設定為1000ms。確保在每個1000ms內都多次觸發click持續2000ms。
// 執行1次(最後一次點選1000ms後) btnDom.addEventListener('click', debounce(clickBtn, 1000)); // 執行3次(點選立即執行一次、1000ms後執行一次,2000ms後執行一次) btnDom.addEventListener('click', throttle(clickBtn, 1000));
debounce使用場景:
第一次觸發後,進行倒計wait毫秒,如果倒計時過程中有其他觸發,則重置倒計時;否則執行fn。用它來丟棄一些重複的密集操作、活動,直到流量減慢。例如:
- 對使用者輸入的驗證,不在輸入過程中就處理,停止輸入後進行驗證足以;
- 提交ajax時,不希望1s中內大量的請求被重複傳送。
throttle使用場景
第一次觸發後先執行fn(當然可以通過{leading: false}來取消),然後wait ms後再次執行,在單位wait毫秒內的所有重複觸發都被拋棄。即如果有連續不斷的觸發,每wait ms執行fn一次。與debounce相同的用例,但是你想保證在一定間隔必須執行的回撥函式。例如:
- 對使用者輸入的驗證,不想停止輸入再進行驗證,而是每n秒進行驗證;
- 對於滑鼠滾動、window.resize進行節流控制。
正真的業務場景:
一個相當常見的例子,使用者在你無限滾動的頁面上向下滾動滑鼠載入頁面,你需要判斷現在距離頁面底部多少。如果使用者快接近底部時,我們應該傳送請求來載入更多內容到頁面。在此debounce沒有用,因為它只會在使用者停止滾動時觸發,但我們需要使用者快到達底部時去請求。通過throttle我們可以不間斷的監測距離底部多遠。
$(document).ready(function(){ // 這裡設定時間間隔為300ms $(document).on('scroll', throttle(function(){ check_if_needs_more_content(); }, 300)); // 是否需要載入更多資源 function check_if_needs_more_content() { var pixelsFromWindowBottomToBottom = 0 + $(document).height() - $(window).scrollTop() - $(window).height(); // 滾動條距離頁面底部小於200,載入更多內容 if (pixelsFromWindowBottomToBottom < 200){ // 載入更多內容 $('body').append($('.item').clone()); } } });
特別說明:
// 錯誤 $(window).on('scroll', function() { debounce(doSomething, 300); }); // 正確 $(window).on('scroll', debounce(doSomething, 200));
謝謝你請我吃糖果