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

防抖(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)和防抖的区别在哪
节流是固定时间间隔内最多执行一次,不管触发多频繁。适用于监听滚动、鼠标移动这类持续高频但只需“采样”的场景。
常见错误是把节流写成“每次触发都重设定时器”,结果变成变相防抖;正确做法应区分“正在等待”和“可立即执行”两种状态。
立即学习“Java免费学习笔记(深入)”;
- 使用时间戳方式:记录上次执行时间,当前时间减去它大于等于
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绘图/拖拽反馈、无限滚动加载更多(不能等用户停下才加载) - 特别注意:
scroll和resize在 Chrome 中可能每秒触发上百次,不用节流或防抖极易导致页面卡死
现代开发中要不要自己手写
Lodash 的 debounce 和 throttle 已经非常稳定,支持取消(cancel)、刷新(flush)等扩展能力,日常开发优先直接引入。
但手写仍有价值:调试时能看清执行路径;某些轻量项目不想引入依赖;面试中考察对异步与闭包的理解深度。
- 注意浏览器原生的
requestIdleCallback或IntersectionObserver可替代部分节流需求(如懒加载) - Vue/React 中封装成 Hook 或 Composable 更自然,但底层逻辑仍是上述两种模式之一
- 移动端触摸事件(
touchmove)比mousemove更频繁,节流阈值建议设为16ms(接近 60fps)而非默认100ms
防抖和节流不是“加了就稳”,它们改变的是函数执行时机,稍不注意就会让 UI 响应变迟钝,或者漏掉关键状态。真正难的从来不是实现,而是判断某次 scroll 是该节流、防抖,还是干脆换用 IntersectionObserver。










