防抖(Debounce)和节流(Throttle)

5 阅读4分钟

防抖(Debounce)和节流(Throttle)是两种截然不同的技术,虽然它们的目的大致相同——都是为了限制函数的执行频率,避免在短时间内触发大量重复操作导致性能问题(例如resize、scroll、mousemove、输入框校验等)。但它们解决问题的思路和适用场景完全不同。

你可以用一个非常形象的比喻来理解它们:


1. 防抖 (Debounce) - “最后一个人说了算”

核心思想:事件被触发后,等待一段“冷静期”(比如n毫秒)。如果在这个“冷静期”内事件又被触发,则重新开始计时。只有当事件停止触发并等待完整的n毫秒后,函数才会被执行一次。

生活比喻电梯门
电梯门开着,等人进来。如果不断有人进来(不断触发事件),电梯门会一直保持开着(重置计时器),直到最后一个人进来后,等待一段时间(比如5秒)没人再来了,电梯门才会关闭(执行函数)。

代码典型应用场景

  • 搜索框输入联想:用户输入过程中不发送请求,只在用户停止输入(比如500毫秒内没再输入)后才发送请求,极大减少服务器压力。
  • 窗口大小调整(resize) :等待用户拖拽浏览器窗口结束后,再计算布局,避免频繁重排重绘。
  • 文本编辑器自动保存:用户停止编辑一段时间后,才进行自动保存。

简单代码实现(概念性)

function debounce(func, wait) {
  let timeout;
  return function() {
    clearTimeout(timeout); // 清除之前的计时
    timeout = setTimeout(func, wait); // 重新开始计时
  };
}

2. 节流 (Throttle) - “按规定时间执行”

核心思想:保证在一个固定的时间间隔内,函数最多只被执行一次。就像水龙头一样,无论你开得多大,它都会按照固定的速率滴水。

生活比喻游戏里的技能冷却(CD)
你疯狂地按一个技能键(频繁触发事件),但这个技能有2秒的冷却时间。你按第一次,技能立即释放(函数立即执行),之后2秒内你再怎么按,技能都不会释放。直到2秒冷却结束,你按的下一次才会再次触发技能。

代码典型应用场景

  • 页面滚动加载(scroll) :在用户滚动页面时,每隔一定时间(比如250ms)才检查一次滚动位置,判断是否需要加载更多,而不是像素级滚动都触发。
  • 鼠标移动(mousemove) :比如实现一个元素跟随鼠标的效果,不需要每像素移动都计算位置,每隔100ms计算一次就足够流畅。
  • 按钮频繁点击:防止用户疯狂提交表单,比如规定1秒内只能提交一次。

简单代码实现(概念性)

function throttle(func, wait) {
  let canRun = true; // 通过闭包保存一个标记
  return function() {
    if (!canRun) return; // 冷却中,直接返回
    canRun = false; // 立即进入冷却状态
    setTimeout(() => {
      func();
      canRun = true; // 冷却时间结束,重置状态
    }, wait);
  };
}

核心区别总结表

特性防抖 (Debounce)节流 (Throttle)
核心思想事件停止触发后,再等待一段时间才执行。 “只为最后一次触发等待”固定时间间隔内,只执行一次。 “按规定速率执行”
执行时机在连续触发的末尾执行在连续触发的过程中按间隔执行
是否重置计时,每次新触发都会重置等待计时器,计时器独立运行,不受新触发影响
生活比喻电梯门技能冷却(CD)
典型场景搜索联想、Resize结束操作滚动加载、鼠标移动、抢购按钮

如何选择?

  • 如果你关心的是  “最终状态” (比如用户输入完了什么),用 防抖
  • 如果你关心的是  “过程状态” ,需要在一个持续的操作中 定期执行(比如滚动时定期检查位置),用 节流

希望这个解释和比喻能让你彻底分清它们!