JavaScript 中的调节器:提高应用程序的性能

发布于 1月9日  约 12 分钟
作者:Trey Huffine

翻译:疯狂的技术宅

原文:https://levelup.gitconnected....

未经允许严禁转载

调节器是浏览器中通过限制代码要处理的事件数量来提高性能的常用技术。当你想以受控的速率执行回调时,应该使用调节器,它允许你在每个固定的时间间隔内重复处理过渡状态。

我将以一个真实世界的类比开始,然后在 Web 上下文中描述调节器,最后提供有关如何实现节流的注释代码示例。在文章的结尾,有一个带有调节器示例的 Codepen,你可以与之交互以查看其工作原理。如果只关心代码,请跳至 “JavaScript 中的调节器实现” 部分。

调节器是“去抖动” 的表亲,它们都可以提高 Web 应用的性能。但是它们在不同的情况下使用。当你只关心最终状态时,会使用去抖功能。例如等待用户停止键入以获取预先输入的搜索结果。当你想要以受控的速率处理所有中间状态时,最好使用调节器。例如,当用户调整窗口大小并在页面内容改变时重新排列页面内容时跟踪屏幕宽度,而不是等到用户完成操作时再跟踪。

12.gif

真实世界中调节器的例子

一个比喻是我们的饮食方式。我们想节制饮食,以便每 6 小时吃一顿饭。我们早上 7 点起床吃早餐,然后节流,直到下午 1 点吃午餐,最后在晚上 7 点吃晚餐。每次吃完饭后,我们就会阻止自己进食 6 个小时,以确保整天都能以合理的增量获得食物。

这种类比可以扩展到生活中以设定的增量去执行动作的任何情形。例如,我们希望每三个月更换一次汽车中的机油。我们不会提前这样做,因为那是在浪费金钱,我们也不会拖延,因为这会损坏汽车引擎。我们会检查挡风玻璃上的贴纸,看是否经过了足够的时间,然后我们去找机械师。因此,我们会每 3 个月就进行一次换油,这样可以最有效地处理换油事件。

Web 开发中的节流

为了理解 Web 开发上下文中的限制,假设你有一个滚动事件处理程序,当用户在页面上向下移动时,你想在其中向用户显示新内容。如果在每次用户滚动单个像素时都执行回调,假如快速滚动的话,我们将会很快就被事件阻塞,因为它将快速连续发送数百或数千个事件。相反,我们对其进行限制,仅每 100 毫秒检查一次滚动,这样每秒仅获得10个回调。用户仍然可以立即感觉到响应,但是计算效率更高。

调节器用于创建均匀间隔的函数调用。想象一下,如果你在事件处理程序回调函数中执行大量计算或 API 请求。通过限制这些回调,可以防止应用冻结或对服务器发出不必要地请求。

JavaScript 中的调节器的实现

让我们立即进入调节器代码。我会在下面进行描述,然后提供该功能的注释版本。

const throttle = (callback, delay) => {
  let throttleTimeout = null;
  let storedEvent = null;

  const throttledEventHandler = event => {
    storedEvent = event;

    const shouldHandleEvent = !throttleTimeout;

    if (shouldHandleEvent) {
      callback(storedEvent);

      storedEvent = null;

      throttleTimeout = setTimeout(() => {
        throttleTimeout = null;

        if (storedEvent) {
          throttledEventHandler(storedEvent);
        }
      }, delay);
    }
  };

  return throttledEventHandler;
};
这个调节器的实现是最简单易懂的。它仅用于教学目的,并非是效率最高或代码行数最少的。

调节器是一个高阶函数,这是一个返回另一函数的函数(为清楚起见,此处命名为 throttledEventHandler)。这样做是为了围绕 callbackdelaythrottleTimeoutstoredEvent 参数形成一个闭包。这保留了在执行 throttledEventHandler 时要读取的每个变量的值。以下是每个变量的定义:

  • callback:你想要以给定速率执行的节流函数。
  • delay:你希望节流函数在多次执行 callback 之间等待的时间。
  • throttleTimeout: The value used to indicate a running throttle created by our setTimeout.
  • throttleTimeout:该值用于指示由 setTimeout 创建的调节器。
  • storedEvent:你想通过节流 callback 处理的事件。该值将不断更新,直到截流结束。

我们可以在以下代码中使用调节器:

var returnedFunction = throttle(function() {
  // Do all the taxing stuff and API requests
}, 500);

window.addEventListener('scroll', returnedFunction);

由于调节器返回一个函数,因此第一个例子中的 throttledEventHandler 和第二个例子中的 returnedFunction 函数实际上是相同的函数。每次用户滚动鼠标时,它将执行 throttledEventHandler /returnedFunction

下面逐步说明在截流函数时会发生什么。首先,我们围绕变量创建一个闭包,以便每次执行时它们都可用于ThrottledEventHandlerThrottledEventHandler 接收到 1 个作为事件的参数。它将事件存储在 storedEvent 变量中。

然后检查运行是否超时(即激活调节器)。如果调节器生效,那么 throttledEventHandler 已经完成了该执行并等待执行回调。如果调节器为非活动状态,则可以用回调函数立即处理该事件。然后调用 setTimeout 并存储超时值,该值表明调节器正在生效。

当 timeout 处于活动状态时,将始终存储最新事件。这时则会跳过回调的执行,这可以使我们免于执行 CPU 密集型任务或调用我们的 API。

setTimeout 结束时,将 throttleTimeout 置为空,这表明该函数不再受到限制并且可以处理事件。如果有一个 storedEvent,我们想立即处理它,这是则会递归地调用 throttledEventHandlersetTimeout 内部的递归调用使我们能够以恒定的速率处理事件。只要有新事件继续发生,它就会在期望的延迟后重复执行相同的处理过程。

该函数的注释版本:

// 传递我们要限制的回调以及限制事件之间的延迟
const throttle = (callback, delay) => {
  // 在这些变量周围创建一个闭包。
  // 它们将在调节器处理的所有事件之间共享。
  let throttleTimeout = null;
  let storedEvent = null;

  // 当调节器处于活动状态时,此函数将处理事件和调节器回调。
  const throttledEventHandler = event => {
    // 每次迭代都更新存储的事件
    storedEvent = event;

    // 如果调节器尚未激活,我们将使用事件执行回调
    const shouldHandleEvent = !throttleTimeout;

    // 如果没有活动的调节器,将执行回调并创建一个新的调节器。
    if (shouldHandleEvent) {
      // 处理我们的事件
      callback(storedEvent);

      // 由于我们使用了已存储的事件,因此将其清空。
      storedEvent = null;

      // 通过设置超时来创建新的限制,以防止在延迟期间处理事件。
      // 超时结束后,如果有存储的事件,则执调节器。
      throttleTimeout = setTimeout(() => {
        // 由于调节器时间已到期,因此我们立即使调节器超时无效。
        throttleTimeout = null;

        // 如果我们有一个存储的事件,则递归调用此函数。
        // 递归使我们能够在事件发生时连续运行。
        // 如果事件停止了,我们的调节器将结束。 如果有新事件发生,它将立即执行。
        if (storedEvent) {
          // 由于超时结束:
          // 1. 由于节流时间现在为 null,因此本递归调用将立即执行 `callback`
          // 2. 它将重新启动调节器 timer,使我们可以重复调节器过程
          throttledEventHandler(storedEvent);
        }
      }, delay);
    }
  };

  // 返回受限制的事件处理作为闭包
  return throttledEventHandler;
};

互动示例

https://codepen.io/treyhuffin...`

总结

对于 JavaScript 开发人员而言,节流是一个非常重要且有益的概念。它是提高 Web 应用性能的常用工具,从头开始实施节流功能还可以增强你的高级 JS 技术,例如闭包、异步事件处理、高阶函数和递归。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


阅读 543发布于 1月9日

推荐阅读
疯狂的技术宅
用户专栏

本专栏文章首发于公众号:前端先锋 。

19916 人关注
302 篇文章
专栏主页
目录