返回课程

节流装饰器

重要性: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

附注:传递给 `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` 将使用最后一个记忆的参数和上下文执行。

第三步运行的不是 `func`,而是 `wrapper`,因为我们不仅需要执行 `func`,还需要再次进入冷却状态并设置超时以重置它。

在沙盒中打开带有测试的解决方案。