JavaScript中什么是防抖与节流_它们适用于哪些场景

防抖是将多次高频事件合并为最后一次执行,核心是每次触发清除前次定时器;节流是固定间隔最多执行一次,需区分“等待中”和“可执行”状态。

防抖(debounce)是什么,怎么写一个可靠版本

防抖是指将多次高频触发的事件,合并为最后一次执行。典型场景是用户在搜索框中输入时,不希望每按一次键就发请求,而是等他停顿下来再查。

关键点在于:每次新触发都会清除前一次的定时器,只保留最后一次的延迟执行。

  • setTimeout 必须被 clearTimeout 清除,否则会累积多个未执行定时器
  • 需要返回函数,方便绑定事件或手动调用
  • 推荐支持立即执行模式(immediate = true),即首次触发立刻执行,后续触发仍按防抖逻辑处理
function debounce(func, wait, immediate = false) {
  let timeout;
  return function(...args) {
    const later = () => {
      timeout = null;
      if (!immediate) func(...args);
    };
    const callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func(...args);
  };
}

节流(throttle)和防抖的区别在哪

节流是固定时间间隔内最多执行一次,不管触发多频繁。适用于监听滚动、鼠标移动这类持续高频但只需“采样”的场景。

常见错误是把节流写成“每次触发都重设定时器”,结果变成变相防抖;正确做法应区分“正在等待”和“可立即执行”两种状态。

  • 使用时间戳方式:记录上次执行时间,当前时间减去它大于等于 wait 才执行
  • 使用定时器方式:设置一个标志位,触发时若无定时器则启动,执行完再清空
  • 时间戳方式更简单,但首次触发立即执行;定时器方式可控制首次是否延后
function throttle(func, wait) {
  let previous = 0;
  return function(...args) {
    const now = +new Date();
    if (now - previous > wait) {
      func(...args);
      previous = now;
    }
  };
}

哪些场景该选防抖,哪些必须用节流

选错策略会导致交互卡顿或数据丢失。核心判断依据是:你关心的是“最终状态”还是“过程变化频率”。

  • 防抖适用:input 搜索、resize 窗口调整后重新布局、表单校验(用户输完再提示)
  • 节流适用:scroll 计算可视区域、mousemove 绘图/拖拽反馈、无限滚动加载更多(不能等用户停下才加载)
  • 特别注意:scrollresize 在 Chrome 中可能每秒触发上百次,不用节流或防抖极易导致页面卡死

现代开发中要不要自己手写

Lodash 的 debouncethrottle 已经非常稳定,支持取消(cancel)、刷新(flush)等扩展能力,日常开发优先直接引入。

但手写仍有价值:调试时能看清执行路径;某些轻量项目不想引入依赖;面试中考察对异步与闭包的理解深度。

  • 注意浏览器原生的 requestIdleCallbackIntersectionObserver 可替代部分节流需求(如懒加载)
  • Vue/React 中封装成 Hook 或 Composable 更自然,但底层逻辑仍是上述两种模式之一
  • 移动端触摸事件(touchmove)比 mousemove 更频繁,节流阈值建议设为 16ms(接近 60fps)而非默认 100ms

防抖和节流不是“加了就稳”,它们改变的是函数执行时机,稍不注意就会让 UI 响应变迟钝,或者漏掉关键状态。真正难的从来不是实现,而是判断某次 scroll 是该节流、防抖,还是干脆换用 IntersectionObserver