防抖是将多次高频事件合并为最后一次执行,核心是每次触发清除前次定时器;节流是固定间隔最多执行一次,需区分“等待中”和“可执行”状态。
防抖是指将多次高频触发的事件,合并为最后一次执行。典型场景是用户在搜索框中输入时,不希望每按一次键就发请求,而是等他停顿下来再查。
关键点在于:每次新触发都会清除前一次的定时器,只保留最后一次的延迟执行。
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);
};
}节流是固定时间间隔内最多执行一次,不管触发多频繁。适用于监听滚动、鼠标移动这类持续高频但只需“采样”的场景。
常见错误是把节流写成“每次触发都重设定时器”,结果变成变相防抖;正确做法应区分“正在等待”和“可立即执行”两种状态。
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 可替代部分节流需求(如懒加载)
仍是上述两种模式之一touchmove)比 mousemove 更频繁,节流阈值建议设为 16ms(接近 60fps)而非默认 100ms
防抖和节流不是“加了就稳”,它们改变的是函数执行时机,稍不注意就会让 UI 响应变迟钝,或者漏掉关键状态。真正难的从来不是实现,而是判断某次 scroll 是该节流、防抖,还是干脆换用 IntersectionObserver。