← 返回题目列表

防抖和节流有什么区别?如何实现?

高频 中等 第 6 / 29 题 更新于 2026/07/27
JavaScript防抖节流性能优化

简化版

防抖是事件停止触发一段时间后再执行,适合搜索输入、窗口 resize 后处理;节流是固定时间间隔内最多执行一次,适合滚动监听、拖拽、按钮高频点击。

详细版

防抖实现:

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

节流实现:

function throttle(fn, delay) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= delay) {
      last = now;
      fn.apply(this, args);
    }
  };
}

区别:防抖重置等待时间,强调“最后一次”;节流控制频率,强调“固定节奏”。

完整版教学

一、防抖的思路

防抖像电梯关门:有人进来就重新计时,直到一段时间没人进来才关门。搜索框联想很适合防抖,用户连续输入时不请求,停下来再请求。

核心是闭包保存 timer,每次触发先取消旧定时器,再创建新定时器。代价是响应会被推迟;如果事件永不停止,纯 trailing 防抖也可能永不执行,因此有些库还提供 maxWait 上限。

二、节流的思路

节流像水龙头限流:不管你触发多频繁,一段时间内只放一次水。滚动事件每秒可能触发几十次,如果每次都计算布局,会很卡。

节流可以用时间戳,也可以用定时器。时间戳版通常立即执行第一次;定时器版通常在间隔结束后执行。节流降低调用频率,却会丢弃或合并中间状态,所以拖拽、绘图等场景要保存最新参数,不能假设每个输入点都会被处理。

三、this 和参数不能丢

封装防抖节流时要注意保留调用时的 this 和参数:

fn.apply(this, args);

如果直接写 fn(),事件对象、组件实例上下文可能丢失。

四、工程中的增强点

成熟实现通常支持:

  • leading:是否立即执行第一次。
  • trailing:结束后是否补一次。
  • cancel:取消等待中的执行。
  • flush:立即执行等待中的函数。

面试手写基础版本即可,但能说出增强点会更像真实工程经验。

五、面试追问与工程落地

面试官可能要求实现“立即执行版防抖”。核心是在第一次触发时先执行,之后 delay 内的触发只重置定时器,等窗口期结束后才允许下一次立即执行。这类需求常见于按钮点击:第一次要立刻响应,后续防重复。

节流也常追问 leading 和 trailing。只有 leading 时,最后一次触发可能被丢掉;只有 trailing 时,第一次响应会延迟。滚动加载、拖拽预览、窗口 resize 的最佳体验往往需要根据业务选择组合。

工程里防抖节流不是万能药。输入搜索除了防抖,还要处理请求竞态:后发请求可能先返回,旧请求可能覆盖新结果。需要 abort、请求序号或只接受最后一次响应。

六、用时间线区分 leading 与 trailing

设等待窗口为 300ms,事件发生在 0ms、100ms、220ms、700ms。纯 trailing 防抖会不断重置计时器,前一组只在 520ms 执行一次,最后一次在 1000ms 执行;leading 防抖则可能在 0ms700ms 立即执行。固定窗口的 leading 节流会在 0ms700ms 执行,而带 trailing 的节流还可能在窗口末尾补上最后参数。

事件:    0 ── 100 ── 220 ───────── 700 ms
防抖:                    执行520         执行1000
节流:    执行0 ───────── 可补300 ── 执行700
策略首次响应高频期间停止后最后一次典型场景
trailing 防抖延迟持续重置保留搜索联想、校验
leading 防抖立即忽略或重置锁取决于配置防重复提交
leading 节流立即按窗口放行可能丢失滚动采样
leading + trailing 节流立即限频尽量保留拖拽预览、进度更新

定时器的 delay 是最早可执行时间,不是准点保证。主线程若被一个 500ms 长任务占用,计划在 300ms 运行的 trailing 回调只能等调用栈空闲后执行,因此业务不能用防抖节流承担精确定时职责。

假设高频滚动每秒触发 120次,直接处理意味着约每 8.3ms 调用一次;用 100ms 节流后,理论上最多约每秒 10次,处理次数下降约 91.7%。若目标是与 60Hz 屏幕逐帧更新视觉效果,还可以用 requestAnimationFrame 合并同一帧内的多次输入,但它解决的是按帧调度,不等于通用节流器。

if (!scheduled) {
  scheduled = true;
  requestAnimationFrame(() => {
    scheduled = false;
    render(latestPosition);
  });
}

先根据业务回答“第一次要不要立刻响应、停止后最后一次能不能丢”,再选择 leading/trailing;只背防抖和节流定义无法决定正确配置。

七、常见误区与追问

  • 误区:防抖只是把多个事件简单延迟相同时间。 trailing 防抖会在每次触发时取消并重建等待,执行点取决于最后一次事件。
  • 误区:节流能保证回调严格每隔固定毫秒执行。 定时器受主线程占用、后台页限速和宿主调度影响,只能限制频率,不能提供硬实时保证。
  • 误区:加入防抖后搜索结果就不会发生竞态。 防抖减少请求数,但旧请求仍可能晚于新请求返回,需要 AbortController、序号或最新请求校验。
  • 追问:为什么实现里要保存 thisargs 包装函数要保持原调用语义,并让 trailing 调用使用正确上下文与最新参数。
  • 追问:cancel 应清理哪些状态? 除清除 timer 外,还应重置上次调用时间、待执行参数和上下文,避免旧窗口影响下次调用。
  • 追问:节流用时间戳和定时器有什么体验差别? 时间戳版容易实现 leading,定时器版容易实现 trailing,成熟实现通常组合两者处理边界。
  • 追问:页面卸载时为什么要取消等待回调? 回调继续执行可能访问已销毁组件,也会让闭包中的参数和状态被额外保留。

八、加强记忆

防抖记“停下来才执行”,节流记“按节奏执行”。输入框搜索、防重复提交常用防抖;滚动、拖拽、resize 高频监听常用节流。