⭐ 推荐:社区规则条款 V1.0

js 防抖节流是什么怎么写

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-19 12:35 ·7 浏览 ·0 回复
内容摘要

说明防抖与节流的区别及适用场景(防抖取最终结果、节流保证过程持续反馈),并给出基于闭包和定时器的实现代码,附延迟取值、立即执行、cancel 清理与首尾执行等注意事项。

防抖(debounce)和节流(throttle)都是用闭包和定时器控制函数执行频率的手段:防抖是“等你停下来再执行”,事件停止触发 n 毫秒后才执行一次;节流是“固定频率执行”,无论触发多密集,每 n 毫秒最多执行一次。搜索框输入用防抖,滚动、拖拽用节流。

防抖和节流有什么区别?

结论:防抖只保留最后一次触发,节流保证单位时间内都有执行,选错会导致功能表现完全相反。

  • 防抖:连续触发 10 次,只在停止后执行 1 次。典型场景是搜索联想输入(用户敲完字才发请求)、窗口 resize 结束再重算布局、表单提交按钮防止重复点击。
  • 节流:连续触发 10 次,在 1 秒窗口内可能执行 2-3 次,频率可控。典型场景是 scroll 监听、mousemove 拖拽、无限滚动加载。

一句话判断:如果你只关心“最终结果”,用防抖;如果过程需要持续反馈(进度、位置、懒加载),用节流。

防抖怎么写?

结论:防抖的核心是每次触发都清掉上一个定时器再重设,同时用 apply 保住 this 和参数。

function debounce(fn, delay = 300, immediate = false) {
  let timer = null;
  function debounced(...args) {
    if (timer) clearTimeout(timer);
    if (immediate && !timer) {
      fn.apply(this, args);          // 首次立即执行
    }
    timer = setTimeout(() => {
      timer = null;
      if (!immediate) fn.apply(this, args);
    }, delay);
  }
  debounced.cancel = () => { clearTimeout(timer); timer = null; };
  return debounced;
}

三个必须注意的点:

  1. delay 取值:搜索框输入建议 300ms,按钮防重复提交建议 500-1000ms,resize 建议 200ms。低于 100ms 基本起不到降低请求量的作用。
  2. immediate 开关决定首次是否立即执行,按钮点击场景通常要设成 true,否则用户会感觉“点了没反应”。
  3. 必须暴露 cancel。组件卸载或在 useEffect 的 cleanup 里调用 cancel(),否则定时器持有闭包引用,会造成内存泄漏,并在卸载后仍触发一次回调报错。

节流怎么写?

结论:节流有两种写法——时间戳版(首次立即执行、末次不执行)和定时器版(首次延迟执行、末次会补一次),生产环境建议用“首尾都执行”的组合版。

时间戳版,适合滚动中需要即时反馈的场景:

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

定时器版,适合需要保证最后一次触发的场景(比如滚动到底部加载更多):

function throttle(fn, interval = 200) {
  let timer = null;
  return function (...args) {
    if (timer) return;
    timer = setTimeout(() => {
      timer = null;
      fn.apply(this, args);
    }, interval);
  };
}

interval 的取值参考:scroll 用 100-200ms,mousemove 用 50-100ms,按钮点击用 500ms。低于 50ms 对用户来说已接近连续,节流收益很小。

实际项目里还要不要手写?

结论:业务代码优先直接用 lodash 的 debounce / throttle,它们已经处理了 this、参数透传、cancel()、flush() 和 leading / trailing 配置;手写版本的价值在于理解原理和应对不能引第三方库的场景。

lodash.debounce(fn, 300, { leading: true, trailing: false }) 对应“首次立即执行、停止后不再执行”;lodash.throttle(fn, 200, { leading: true, trailing: true }) 对应“首尾都执行”。

框架里的两个常见问题:

  • Vue:在 onUnmounted 里调用 debounced.cancel(),否则路由切换后定时器仍会执行并访问已销毁的响应式数据。
  • React:不要在渲染函数里直接调用 debounce(fn, 300) 生成新函数,每次渲染都会产生新实例,防抖完全失效。正确做法是 useMemo / useRef 缓存,或把 fn 用 useCallback 包好再进 useMemo 依赖。

总结一下:防抖靠“清定时器 + 重设”实现末次执行,节流靠“时间戳差”或“定时器占位”实现固定频率;延时按场景取 50-1000ms,this 用 apply 保住,卸载时记得 cancel;能用 lodash 就别手写,手写的意义是知道它内部发生了什么。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2292.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
GJ论坛站长
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~