防抖节流
核心区别
| - | 防抖 debounce | 节流 throttle |
|---|---|---|
| 触发后行为 | 重新计时,只在「停止触发后」执行一次 | 固定间隔内最多执行一次 |
| 关注点 | 等待事件结束 | 控制执行频率 |
| 典型场景 | 搜索框输入联想、窗口 resize、表单校验 | 滚动加载、mousemove、按钮防连点 |
高级面试里,光写出能跑的版本不够,要点在于:this 与参数透传、立即执行(leading)/末次执行(trailing)选项、取消(cancel)能力。
防抖(debounce)
/**
* @param {Function} fn
* @param {number} delay
* @param {boolean} immediate 是否在「第一次触发时」立即执行
*/
function debounce(fn, delay = 300, immediate = false) {
let timer = null;
function debounced(...args) {
// 保留 this 指向(如 DOM 事件里 this 指向元素)
const context = this;
if (timer) clearTimeout(timer);
if (immediate) {
// 没有等待中的定时器时,立即执行一次
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow) fn.apply(context, args);
} else {
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
}
}
// 支持取消(如组件卸载时清理)
debounced.cancel = function () {
clearTimeout(timer);
timer = null;
};
return debounced;
}
节流(throttle)
时间戳实现(首次立即执行),逻辑清晰、易写:
function throttle(fn, delay = 300) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn.apply(this, args);
}
};
}
时间戳 + 定时器结合版:首次立即执行,最后一次也会补上(更接近 lodash 行为):
function throttle(fn, delay = 300) {
let last = 0;
let timer = null;
return function (...args) {
const context = this;
const now = Date.now();
const remaining = delay - (now - last);
if (remaining <= 0) {
// 已超过间隔,立即执行,并清掉可能存在的尾部定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
last = now;
fn.apply(context, args);
} else if (!timer) {
// 还没到时间,安排一次尾部执行,保证最后一次触发不丢
timer = setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(context, args);
}, remaining);
}
};
}
常见追问
- 防抖和节流底层都用什么实现?
setTimeout(节流也可用时间戳),React 里清理要在useEffect的返回函数中调用cancel。 requestAnimationFrame能做节流吗? 能,把回调放进rAF,按屏幕刷新率(约 16.7ms)节流,更适合滚动/动画类场景。- lodash 的
debounce/throttle有哪些选项?leading、trailing、maxWait(防抖也能保证最长等待时间内必执行一次)。