JavaScript 中的节流是什么?

26 阅读6分钟

你构建了一个小型的 React 仪表板,然后发现在调整窗口大小或快速滚动时浏览器会卡顿。图表卡顿,按钮冻结,用户感到很烦躁。我曾经遇到过同样的问题——每次发生微小的调整大小事件时,图表都会重新渲染,而使用较慢机器的用户感觉就像在拖着沉重的负担。

修复方法虽小却有效:节流。我们限制了耗时任务的运行频率,应用又恢复了流畅。在这篇文章中,我将用通俗易懂的语言解释节流的重要性、其工作原理以及如何在 React 中使用它。


🧩 节流为何重要(“如何”之前先说“为什么”)

把节流想象成繁忙人行横道上的交警。没有交警,每个人都会冲过去,造成混乱。有了交警,每隔几秒钟就会有一个人过马路——井然有序,可预测。

在 Web 应用中,诸如scrollresizemousemove和 之类的事件input每秒可能会触发数十次甚至数百次。如果您的处理程序执行繁重的工作(重新渲染、计算布局、调用 API),则在每个事件上运行它将会:

  • 降低浏览器速度(CPU 峰值),
  • 导致许多不必要的网络调用,并且
  • 使 UI 感觉不稳定。

节流功能让你能够控制:每 N 毫秒最多运行一次处理程序。这减少了工作量并保持 UI 的响应速度。


🧠 Throttle 与 Debounce 的简单类比

  • 限流: 保安每 N 秒放行一个人。即使 100 个人等待,也总有一个人能按时通过。 适用于:  scroll、、resize周期性分析。
  • 防抖动: 计时器会等待静音,然后让最后一个人通过。如果继续有人来,它会继续等待。 适用于: 边输入边搜索,需要等到输入停止后再进行。

两者都很有用——根据问题进行选择。


🛠️ 简单的节流实现(纯 JS)

让我们构建一个小的、易于阅读的节流阀功能。

// throttle.js
function throttle(fn, wait = 200) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= wait) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

export default throttle;

🔍 为什么这样做有效(一步一步)

  1. lastCall存储fn上次运行的时间。
  2. 对于每个事件,我们都会检查当前时间(now)。
  3. 如果now - lastCall >= wait,我们运行fn并更新lastCall
  4. 否则,我们将忽略此事件。

这是一个前沿节流阀——它立即运行,然后阻塞直到waitms 通过。


📈 React 用例:节流窗口调整大小

假设你想在窗口调整大小时更新图表布局。当用户拖动窗口角落时,调整大小会持续触发——我们最多每 200 毫秒更新一次即可。

// useWindowSizeThrottled.jsx
import { useEffect, useState } from "react";
import throttle from "./throttle";

export default function useWindowSizeThrottled(wait = 200) {
  const isClient = typeof window !== "undefined";
  const [size, setSize] = useState({
    width: isClient ? window.innerWidth : 0,
    height: isClient ? window.innerHeight : 0,
  });

  useEffect(() => {
    const handleResize = throttle(() => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    }, wait);

    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, [wait]);

  return size;
}

✅ 逐步分解

  1. useState保持当前窗口大小。
  2. 在里面useEffect,我们handleResize使用throttle助手创建一个节流装置。
  3. 我们加入handleResizeresize活动中。
  4. 在清理时,我们删除监听器以避免泄漏。

这可以防止setSize每秒被调用数十次,从而减少重新渲染和 CPU 使用率。


⚙️ 改进 React 的节流阀(useRef 方法)

如果您的组件经常重新渲染,重新创建节流阀可能会导致令人惊讶的行为。useRef保持稳定状态而不导致重新渲染:

import { useEffect, useRef, useState } from "react";

function useWindowSizeThrottledRef(wait = 200) {
  const isClient = typeof window !== "undefined";
  const [size, setSize] = useState({
    width: isClient ? window.innerWidth : 0,
    height: isClient ? window.innerHeight : 0,
  });
  const lastCallRef = useRef(0);

  useEffect(() => {
    function handler() {
      const now = Date.now();
      if (now - lastCallRef.current >= wait) {
        lastCallRef.current = now;
        setSize({ width: window.innerWidth, height: window.innerHeight });
      }
    }

    window.addEventListener("resize", handler);
    return () => window.removeEventListener("resize", handler);
  }, [wait]);

  return size;
}

✨ 为什么useRef有帮助

  • lastCallRef在渲染过程中持续存在,无需触发重新渲染。
  • 处理程序的内部状态在整个组件生命周期内保持稳定。

⚠️ 常见错误和误解

  1. 混淆节流阀和去抖动——根据需要选择。
  2. 不清理监听器——总是在清理时删除事件监听器。
  3. 每次渲染时重新创建节流阀——会导致丢失内部状态;使用useRefuseCallback
  4. 盲目限制 API 调用——如果用户期望即时反馈(搜索),去抖动可能会更好。
  5. 假设油门使事情变得“实时”  ——油门限制频率,但如果wait很大的话,可能会延迟响应。

🧾 真实用例

  • scroll用于粘性标头或分析的节流处理程序。
  • 限制resize布局重新计算的事件。
  • 限制遥测或分析以避免服务器端限制。
  • 网络游戏中限制速率的光标移动处理程序。
  • 通过频繁流或 websockets 控制 UI 更新。

🎯 福利:如何在面试中解释节流

当面试官问“什么是节流?”时,请简短回答并给出一个简单的例子。

🗣️ 简短回答(30秒)

节流限制了函数运行的频率(每个给定间隔最多一次),这可以防止昂贵的处理程序运行过于频繁并提高 UI 性能。

💻 快速示例

绘制或粘贴简单的throttle(fn, wait)代码并解释lastCall+Date.now()逻辑。

🔁 常见后续问题及解答

  • 问: 节流阀和防抖动有什么区别? 答: 节流阀强制保持稳定的速度;防抖动则等待静音。
  • 问: 领先油门和落后油门哪个好? 答: 领先油门会立即启动,然后等待;落后油门会在爆发结束时启动。您可以为两者设置选项。
  • 问: 极端情况? 答: 时间漂移、渲染过程中的处理程序身份以及突发事件后缺少最终调用——准备好讨论解决方案。

🧩 迷你白板挑战

设计可能包括和的throttle(fn, wait, options)位置。绘制一个时间线,显示事件爆发期间何时运行。options``leading``trailing``fn


🧠 关键要点

  • 节流= 每 N毫秒最多运行一次- 非常适合周期性限制。
  • 去抖动= 事件停止后运行 - 非常适合等待用户完成输入。
  • 在 React 中,保持处理程序稳定(useRefuseCallback)并始终删除监听器。
  • wait通过在真实设备上进行测试来选择 ——100-300ms是 UI 工作的常见范围。
  • 记住用户的期望——限制会减少 CPU/网络,但可能会延迟响应。

👋 关于我

大家好,我是 Saurav Kumar — 一名软件工程师,热衷于使用 JavaScript、TypeScript、React、Next.js 和 React Native 构建现代 Web 和移动应用程序。

我正在探索人工智能工具如何加速开发,

并分享适合初学者的教程来帮助其他人更快地成长。作者www.mjsyxx.com