前端面试 P013:节流、防抖与取消待执行任务
来源:方格前端讲面试 · 13. 节流 vs 防抖,面试怎么考?,15 分 06 秒。
回看定位:01:10 节流定义 · 04:00 时间戳实现 · 07:42 防抖实现 · 14:25 需要纠正的总结
先选业务语义
节流限制持续触发过程中的执行频率;防抖让连续触发合并为最后一次安静后的执行。滚动中的持续响应常考虑节流;输入停止后的搜索常考虑防抖。
视频结尾把节流说成“固定周期,至少执行一次”,这是错误的保证。下面的时间戳版本只在收到事件且间隔满足时执行,不会在没有事件时自动补执行,最后一次也可能被丢弃。
首次执行、没有尾调用的节流
function throttle(fn, wait) {
let previous = -Infinity;
return function (...args) {
const now = performance.now();
if (now - previous >= wait) {
previous = now;
return Reflect.apply(fn, this, args);
}
};
}
第一次事件立即执行,wait 内后续事件忽略。performance.now 适合测量经过时间,避免系统时钟调整影响 Date.now 的差值。需要最后一次状态时,应选择支持 trailing 的实现,不能假定这个版本自带。
尾部防抖与取消
function debounce(fn, wait) {
let timer = null;
function wrapped(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
Reflect.apply(fn, this, args);
}, wait);
}
wrapped.cancel = () => {
clearTimeout(timer);
timer = null;
};
return wrapped;
}
普通函数 wrapped 保留调用者 this,内部箭头沿用它;闭包保存本次参数。组件卸载时先 cancel,再按同一个函数引用移除监听器。
实战边界
- 连续输入可能让尾部防抖一直等待;业务需要最长等待时考虑 maxWait。
- 防抖不会自动取消已经发出的网络请求,也不能单独解决旧响应覆盖新响应;还需要 AbortController 或请求序号。
- 延迟触发不是精确计时,仍受事件循环和后台节流影响。
- 若需求是“真正释放拖拽后执行”,使用 dragend、pointerup 等实际结束事件,而不是把一段无输入误当作结束。
- 动画相关工作还应考虑 requestAnimationFrame;布局监听也可能更适合 ResizeObserver、IntersectionObserver。
面试时明确 leading、trailing、取消、参数和 this 的策略,比只给出函数名更有价值。