怎么实现javascript的防抖与节流_它们在哪些场景下特别有用?

防抖需用闭包保存timerId并每次先clearTimeout,节流用setTimeout或requestAnimationFrame实现固定频率执行;搜索框用防抖,滚动监听多用节流,触底检测用防抖。

防抖(debounce)怎么写才不会清不掉定时器

防抖的核心是「等用户停手再执行」,常见于搜索框输入、窗口缩放监听。关键陷阱是 clearTimeout 没清对上一次的定时器,导致旧回调仍会触发。

必须用闭包保存上一个 timerId,且每次新触发时先清除它:

function debounce(fn, delay) {
  let timerId = null;
  return function(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}
  • 不要在函数外定义 timerId(否则多个 debounce 实例互相干扰)
  • 使用 fn.apply(this, args) 保证 this 和参数正确传递
  • 如果需要立即执行首次调用(leading edge),得额外加标志位和逻辑,标准防抖默认不支持

节流(throttle)用 setTimeout 还是 requestAnimationFrame

节流是「固定频率执行」,适合鼠标移动、滚动监听。两种主流实现方式差异明显:

  • setTimeout 版本:简单可靠,兼容性好,但可能略滞后于真实事件节奏
  • requestAnimationFrame 版本:更贴合屏幕刷新率(通常 60fps),视觉更顺滑,但不适用于需要精确时间间隔的场景(比如每 200ms 发一次请求)

基础 setTimeout 节流示例:

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

注意:lastTime 初始值不能设为 0 以外的固定值,否则首次调用可能被跳过。

搜索框该用防抖还是节流?别只看“有没有输入”

搜索框输入后发起请求,99% 的情况该用防抖——因为用户真正关心的是「最终输入结果」,不是中间过程。节流在这里反而容易漏掉最后一次输入。

  • 防抖延迟设为 300~500ms 比较合理;太短用户还没打完就发了,太长响应迟钝
  • 如果后端支持增量建议(如输入“a”返回“apple”,再输“p”直接补全),可配合防抖 + 取消上一个 pending 请求(用 AbortController
  • 节流在搜索场景下只有一种合理用途:限制「用户主动点击「搜索」按钮」的频率,防止重复提交

滚动监听里防抖和节流的性能影响差在哪

滚动事件(scroll)非常频繁,不加控制直接绑定回调会导致严重卡顿。这里节流更常用,但防抖也有其不可替代的用途:

  • 节流(如每 100ms 执行一次):适合做「滚动中持续更新吸顶导航」「实时计算可视区域」
  • 防抖(如延迟 150ms):适合做「滚动停止后加载更多」或「滚动到底部触底检测」,因为你要等用户真停下来才行动
  • 现代方案建议优先用 IntersectionObserver 替代手动监听 scroll,它天然异步且性能更好,防抖/节流都可省掉

别忘了加 passive: trueaddEventListener 选项里,否则移动端滚动会强制同步执行 JS,直接卡死。