防抖的做用是將多個連續的debounced
調用合併爲一次func
調用。做用見參考資料1。css
debounced
調用的間隔小於waitTime
,則視爲連續的調用。debounced
調用已通過去了waitTime
的時間,則說明該輪連續調用已經結束(進入穩定狀態)。這個時間點也被稱爲trailing edge。debounced
調用是下一輪連續調用的開始。固然,第一次debounced
調用也是一輪連續調用的開始。這個時間點也被稱爲leading edge。immediate
參數能夠控制是否在leading edge執行一次func
調用。callAfterStable
參數控制是否在trailing edge執行一次func
調用。所以,func
調用能夠放在連續調用開始時,也能夠放在結束時,也能夠都放。通常設置immediate = false,callAfterStable = true
,將func
調用放在連續調用結束時。debounced
的調用一直持續不斷,且相鄰間隔都小於waitTime
,則意味着連續調用一直沒有結束,放在trailing edge的func
調用一直不會執行。function debounce( func, waitTime = 1000, immediate = false, callAfterStable = true ) { if (!immediate && !callAfterStable) throw new Error("immediate 和 callAfterStable 不能同時爲false"); // 不然func.apply永遠不會調用 let timeout = null; const debounced = function(...args) { // timeout的值決定當前是否處於穩定狀態(已經通過waitTime沒有被調用了) // 若是已經存在一個定時器,說明如今是處於一輪連續調用當中(非穩定狀態),須要從新計時 if (timeout) clearTimeout(timeout); // 不然,此時是leading edge。若是配置了immediate,此時要觸發func else if (immediate) func.apply(this, args); // trailing edge將在waitTime時間之後到來,進入穩定狀態(前提是這段時間內沒有被調用) timeout = setTimeout(() => { // 這個回調被執行時,說明已經通過waitTime沒有被調用了,進入穩定狀態 timeout = null; // 此時是trailing edge。若是配置了callAfterStable,要觸發func if (callAfterStable) func.apply(this, args); }, waitTime); }; // 使用者能夠調用這個函數,強行進入穩定狀態 debounced.forceStabilize = function() { if (timeout) { clearTimeout(timeout); timeout = null; } }; return debounced; }
節流的做用是限制func
調用的頻率(最多每waitTime
調用一次)。做用見參考資料2。html
防抖與節流之間的重要區別是,防抖是基於上次debounced
調用來計算waitTime
的;而節流是基於上次func
調用來計算waitTime
的,只要距離上次func
調用超過了waitTime
,就能夠進行下次func
調用。git
實現2修改自參考資料2。我的認爲實現1更好理解。github
// immediate傳入true,將在leading edge就第一次調用func // 不然,將在 leading edge+waitTime 的時候才第一次調用func function throttle(func, waitTime = 1000, immediate = true) { let timeout = null, // called表示自從上次func調用之後,是不是否有調用過throttled called, // 存儲上一次調用throttled時提供的args和this,用來在timeExpired時調用func lastArgs, lastThis; function timeExpired() { if (called) { func.apply(lastThis, lastArgs); called = false; timeout = setTimeout(timeExpired, waitTime); } else { // trailing edge // trailing edge不調用func了, // 由於在waitTime以前調用過了func,且自從那之後,throttled就沒有被調用過。 timeout = null; // 釋放內存 lastArgs = lastThis = null; } } function throttled(...args) { lastArgs = args; lastThis = this; if (!timeout) { // leading edge if (immediate) { func.apply(lastThis, lastArgs); called = false; } else { // !immediate時,leading edge下一次的timeExpired必須調用func // 不然,若是在(leading edge, leading edge + waitTime]這段時間內沒有調用過throttled,func一次也不會執行 called = true; } timeout = setTimeout(timeExpired, waitTime); } else { called = true; } } throttled.cancle = function() { if (timeout) { clearTimeout(timeout); timeout = null; lastArgs = lastThis = null; } }; return throttled; }
function throttle( func, waitTime = 1000, immediate = true, callAfterStable = true ) { if (!immediate && !callAfterStable) throw new Error("immediate 和 callAfterStable 不能同時爲false"); // 下面會指出緣由 let timeout = null, // 上一次調用func的時間 previous = 0; const throttled = function(...args) { const now = Date.now(); // immediate==false時,previous==0有特殊的含義:當前處於穩定狀態,本次調用throttled不當即觸發func // 阻止當即觸發func的方式:previous = now,至關於0秒前剛剛調用過了func // 所以穩定狀態下的第一次throttled調用會進入elseif,將func推遲調用 if (!previous && !immediate) previous = now; const remain = waitTime - (now - previous); // immediate 和 callAfterStable 不能同時爲false,不然if和elseif語句塊都永遠不會調用 if (remain < 0 || remain > waitTime) { // 距離上一次調用func至少通過了waitTime,本次throttled當即觸發func if (timeout) { // 有可能有timer回調仍阻塞在時間隊列中(雖然確定已經超時),銷燬它 clearTimeout(timeout); timeout = null; } func.apply(this, args); previous = now; } else if (!timeout && callAfterStable) { // throttled調用時,距離上一次調用func尚未過去waitTime, // 不當即觸發func,而是安排到previous+waitTime時刻 // 判斷!timeout是爲了防止安排多個func在previous+waitTime時刻調用 timeout = setTimeout(() => { func.apply(this, args); // immediate==false時,previous=0表示進入穩定狀態,設置它是爲了阻止下一次的immediate調用 previous = immediate ? Date.now() : 0; timeout = null; }, remain); } }; throttled.forceStabilize = function() { previous = 0; if (timeout) { clearTimeout(timeout); timeout = null; } }; return throttled; }
<!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1"> <title>test</title> <style> #container { width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; } </style> </head> <body> <div id="container"></div> <script src="lib.js"></script> <script> var count = 1; var container = document.getElementById("container"); function getUserAction() { container.innerHTML = count++; } // container.onmousemove = debounce(getUserAction); container.onmousemove = throttle(getUserAction); </script> </body> </html>