防抖需用闭包保存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 节流示例:
立即学习“Java免费学习笔记(深入)”;
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: true 到 addEventListener 选项里,否则移动端滚动会强制同步执行 JS,直接卡死。











