返回課程

節流裝飾器

重要性:5

建立一個「節流」裝飾器 throttle(f, ms),它會傳回一個包裝函式。

當它被呼叫多次時,它會在每 ms 毫秒最多傳遞一次呼叫給 f

與防抖裝飾器相比,行為完全不同

  • debounce 在「冷卻」時間後執行函式一次。適合處理最終結果。
  • throttle 執行它的頻率不會比給定的 ms 時間更頻繁。適合不應太頻繁的定期更新。

換句話說,throttle 就像一位秘書,接受電話,但不會比每 ms 毫秒一次更頻繁地打擾老闆(呼叫實際的 f)。

讓我們檢查實際應用,以更了解該需求,並了解其來源。

例如,我們想要追蹤滑鼠移動。

在瀏覽器中,我們可以設定一個函式,在每次滑鼠移動時執行,並在移動時取得指標位置。在滑鼠使用期間,此函式通常會非常頻繁地執行,可能每秒執行 100 次(每 10 毫秒)。我們希望在指標移動時更新網頁上的某些資訊。

…但是更新函式 update() 太過龐大,無法在每次微小移動時執行。每 100 毫秒更新一次就已足夠,更頻繁地更新也沒有意義。

因此,我們會將其包裝到裝飾器中:使用 throttle(update, 100) 作為在每次滑鼠移動時執行的函式,而不是原始的 update()。裝飾器會經常被呼叫,但最多每 100 毫秒將呼叫轉發到 update() 一次。

視覺上,它會看起來像這樣

  1. 對於第一次滑鼠移動,裝飾過的變體會立即將呼叫傳遞給 update。這很重要,使用者會立即看到我們對其移動的反應。
  2. 然後,隨著滑鼠繼續移動,直到 100 毫秒,什麼事都不會發生。裝飾過的變體會忽略呼叫。
  3. 100 毫秒 結束時,會再執行一次 update,並使用最後的座標。
  4. 最後,滑鼠會停止在某個地方。裝飾過的變體會等到 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

  1. 在第一次呼叫期間,wrapper 只會執行 func 並設定冷卻狀態(isThrottled = true)。
  2. 在此狀態下,所有呼叫都會記在 savedArgs/savedThis 中。請注意,內容和參數都同樣重要,且應該記住。我們需要同時使用它們來重現呼叫。
  3. ms 毫秒經過後,setTimeout 會觸發。冷卻狀態會被移除(isThrottled = false),而且如果我們忽略了呼叫,wrapper 會使用最後記住的參數和內容執行。

第 3 步不會執行 func,而是執行 wrapper,因為我們不僅需要執行 func,還需要再次進入冷卻狀態並設定計時器來重設它。

在沙盒中開啟包含測試的解決方案。