防抖是“等最后一次”,节流是“固定间隔执行一次”;防抖适用于搜索框输入等需最终状态的场景,节流适用于滚动监听等需过程状态的场景。

防抖和节流不是“选一个用”,而是解决两类不同触发场景的问题:防抖适合「等用户彻底停手再执行」,节流适合「匀速稳定地响应高频事件」。
防抖(debounce)怎么写才不踩坑
核心是每次触发都清除上一次定时器,只保留最后一次的延时执行。常见错误是没处理 this 和参数透传,或忘记返回函数以便移除监听。
- 必须用
clearTimeout清除前序定时器,否则会累积执行 - 用
func.apply(context, args)保证原函数的this和参数正确传递 - 若需手动取消(比如组件卸载),得暴露
cancel方法 - 立即执行模式(leading edge)要单独判断,不能和 trailing 混在一起
简版实现:
function debounce(func, wait, immediate = false) {
let timeout;
return function(...args) {
const later = () => {
timeout = null;
if (!immediate) func.apply(this, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
}节流(throttle)为什么常用时间戳+定时器双机制
单靠 setTimeout 容易漏掉最后一次触发;只用时间戳又无法保证「至少执行一次」。双机制能兼顾首尾和均匀性。
立即学习“Java免费学习笔记(深入)”;
- 时间戳方案:记录上次执行时间,当前时间减去它 ≥ wait 才执行,执行后更新时间戳
- 定时器方案:触发时若无 pending 定时器,则设一个,在结束时清空;但可能丢尾
- 推荐组合:用时间戳控制是否该执行,用定时器兜底保证最终执行(即「有头有尾」)
- 注意不要在滚动/拖拽中直接用
requestAnimationFrame替代节流——它不控制频率,只对齐帧率
可靠节流示例(时间戳 + 定时器):
function throttle(func, wait) {
let previous = 0;
let timeout = null;
return function(...args) {
const now = Date.now();
if (now - previous >= wait) {
func.apply(this, args);
previous = now;
} else {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
previous = Date.now();
}, wait);
}
};
}input搜索、窗口缩放、按钮点击该用哪个
选型取决于「用户意图」和「业务语义」,不是看谁更“高级”。
-
input搜索建议:用防抖(debounce),等用户打完字再发请求,避免中间态浪费 API 调用 -
resize或scroll监听:优先节流(throttle),尤其涉及重排重绘时,需稳定节奏更新 UI - 防二次提交按钮:用防抖(
debounce),限制「连续点击」,但要注意首次点击必须立刻生效(immediate = true) - 鼠标拖拽位置上报:节流更稳妥,防抖会导致松手前位置丢失,体验断层
Lodash 的 debounce 和 throttle 哪些配置容易被忽略
它们默认行为和手写差异不小,尤其在边缘场景下表现不同。
-
leading: true+trailing: false组合会让防抖变成「首次立即执行,后续只清不延」,容易误以为失效 -
maxWait是节流的强制上限,即使一直触发,也会在maxWait后兜底执行——这点手写常遗漏 -
cancel()和flush()在异步清理或同步触发剩余任务时很关键,但多数人只记得调用主函数 - Lodash 版本 ≥ 4.0 后,
throttle默认启用leading和trailing,意味着首尾都会执行,和纯时间戳手写版行为不一致
真正难的不是写出一个能跑的版本,而是想清楚「这次用户动的是什么,系统该在什么时候、以什么节奏回应」——防抖和节流只是两个杠杆,支点在业务逻辑里。











