debounce 和 throttle 本质不同:debounce 等“停”,throttle 控“频”;输入框搜索必须用 debounce,scroll/resize 推荐 throttle;二者均需正确处理 this、定时器清除及初始化。
debounce 和 throttle 不是“要不要用”的问题,而是“用错就白优化”的问题。它们本质都是对高频事件的节制策略,但逻辑完全不同:一个等“停”,一个控“频”。
debounce 怎么写才不漏清定时器?核心陷阱是:clearTimeout 作用在了错误的 timerId 上,或者没处理 undefined 初始值导致报错。
timerId,不能挂全局或依赖外部变量timerId 初始化为 null 或 undefined,否则首次 clearTimeout(undefined) 虽不报错,但容易掩盖逻辑漏洞this 必须透传,推荐用 func.apply(this, args),别直接 func(...args)(会丢掉原始上下文)immediate 模式时,“立即执行”和“延迟执行”不能共存,要靠 !timerId 判断是否首次function debounce(func, delay, immediate = false) {
let timerId = null;
return function(...args) {
const callNow = immediate && !timerId;
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
if (!immediate) func.apply(this, args);
}, delay);
if (callNow) func.apply(this, args);
};
}throttle 为什么时间戳版比定时器版更稳?定时器版(setTimeout 锁定 + 解锁)在极端高频触发下可能“吞掉”首尾调用;时间戳版用 Date.now() 记录上次执行时间,每次只比对间隔,逻辑更线性、无状态依赖。
_.throttle({ leading: true, trailing: true })
Date.now() 在低性能设备上可能有毫秒级偏差,但对前端节流影响可忽略function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}debounce 还是 throttle?用错就变卡顿或漏请求——输入框必须用 debounce,因为用户关心的是“最终输入结果”,不是中间过程。
throttle:用户打 “hello” 时,可能第 1 次(h)、第 3 次(l)、第 5 次(o)各发一次请求,浪费且无意义debounce(500ms):用户停顿半秒后才查 “hello”,精准、省资源事件回调里直接传 debounce(handleScroll),但 handleScroll 内部用了 this.xxx,运行时 this 就变成 window —— 因为防抖返回的是新函数,未绑定上下文。
.bind(this),或改用箭头函数包装,或把 this 显式存为变量useDebounceCallback),自动绑定console.log('this:', this),确认是否为预期 DOM 元素或组件实例实际项目里,最易被忽略的不是实现,而是「混合场景」:比如一个按钮既要防抖(防止重复提交),又要节流(限制每秒最多点 2 次)。这时候得叠加策略,或换用带优先级的节流逻辑——简单函数应付不了所有真实交互。