節流裝飾器
重要性:5
建立一個「節流」裝飾器 throttle(f, ms)
,它會傳回一個包裝函式。
當它被呼叫多次時,它會在每 ms
毫秒最多傳遞一次呼叫給 f
。
與防抖裝飾器相比,行為完全不同
debounce
在「冷卻」時間後執行函式一次。適合處理最終結果。throttle
執行它的頻率不會比給定的ms
時間更頻繁。適合不應太頻繁的定期更新。
換句話說,throttle
就像一位秘書,接受電話,但不會比每 ms
毫秒一次更頻繁地打擾老闆(呼叫實際的 f
)。
讓我們檢查實際應用,以更了解該需求,並了解其來源。
例如,我們想要追蹤滑鼠移動。
在瀏覽器中,我們可以設定一個函式,在每次滑鼠移動時執行,並在移動時取得指標位置。在滑鼠使用期間,此函式通常會非常頻繁地執行,可能每秒執行 100 次(每 10 毫秒)。我們希望在指標移動時更新網頁上的某些資訊。
…但是更新函式 update()
太過龐大,無法在每次微小移動時執行。每 100 毫秒更新一次就已足夠,更頻繁地更新也沒有意義。
因此,我們會將其包裝到裝飾器中:使用 throttle(update, 100)
作為在每次滑鼠移動時執行的函式,而不是原始的 update()
。裝飾器會經常被呼叫,但最多每 100 毫秒將呼叫轉發到 update()
一次。
視覺上,它會看起來像這樣
- 對於第一次滑鼠移動,裝飾過的變體會立即將呼叫傳遞給
update
。這很重要,使用者會立即看到我們對其移動的反應。 - 然後,隨著滑鼠繼續移動,直到
100 毫秒
,什麼事都不會發生。裝飾過的變體會忽略呼叫。 - 在
100 毫秒
結束時,會再執行一次update
,並使用最後的座標。 - 最後,滑鼠會停止在某個地方。裝飾過的變體會等到
100 毫秒
到期,然後使用最後的座標執行update
。因此,非常重要的是,最後的滑鼠座標會被處理。
程式碼範例
function f(a) {
console.log(a);
}
// f1000 passes calls to f at maximum once per 1000 ms
let f1000 = throttle(f, 1000);
f1000(1); // shows 1
f1000(2); // (throttling, 1000ms not out yet)
f1000(3); // (throttling, 1000ms not out yet)
// when 1000 ms time out...
// ...outputs 3, intermediate value 2 was ignored
P.S. 傳遞給 f1000
的參數和內容 this
應該傳遞給原始的 f
。
function throttle(func, ms) {
let isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) { // (2)
savedArgs = arguments;
savedThis = this;
return;
}
isThrottled = true;
func.apply(this, arguments); // (1)
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
呼叫 throttle(func, ms)
會傳回 wrapper
。
- 在第一次呼叫期間,
wrapper
只會執行func
並設定冷卻狀態(isThrottled = true
)。 - 在此狀態下,所有呼叫都會記在
savedArgs/savedThis
中。請注意,內容和參數都同樣重要,且應該記住。我們需要同時使用它們來重現呼叫。 - 在
ms
毫秒經過後,setTimeout
會觸發。冷卻狀態會被移除(isThrottled = false
),而且如果我們忽略了呼叫,wrapper
會使用最後記住的參數和內容執行。
第 3 步不會執行 func
,而是執行 wrapper
,因為我們不僅需要執行 func
,還需要再次進入冷卻狀態並設定計時器來重設它。