防抖函数核心是每次触发清除前次定时器并重设,确保只执行最后一次;需用闭包隔离 timer、用 apply 保持 this 和参数,基础写法:function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; }
防抖的核心逻辑是:每次触发都重置定时器,只执行最后一次。常见错误是直接在全局声明 timer 变量,导致多个防抖实例互相干扰。
正确做法是用闭包保存每个实例独立的 timer:
function debounce(fn, delay) {
let timer = null;
return function (.
..args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}fn 必须能接收 apply 传入的参数,否则事件对象或输入值会丢失this 指向——绑定在 DOM 元素上时,fn 内部的 this 就是该元素;若需固定上下文,建议提前用 fn.bind(context)
节流要保证「单位时间内最多执行一次」,但两种写法行为不同:
✅ 时间戳版(首次立即触发):
function throttleByTimestamp(fn, limit) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= limit) {
fn.apply(this, args);
last = now;
}
};
}✅ 定时器版(末次可能延迟执行):
function throttleByTimer(fn, limit) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, limit);
}
};
}limit 才执行useEffect 依赖项变化会失效不是所有高频事件都要加,重点看是否引发副作用:
input 事件做实时搜索 → 必须防抖,否则每打一个字都发请求resize 改变布局 → 推荐节流(如更新 canvas 尺寸、重排网格),避免重绘风暴scroll 实现吸顶或懒加载 → 节流更稳妥,防抖会导致滚动停止后才计算,体验卡顿requestAnimationFrame)→ 不要用防抖/节流,它本身就是天然节流机制真实业务里最常踩坑的不是写法,而是上下文和清理时机:
setTimeout,会报 Can't perform a React state update on an unmounted component
onClick 处理器时,event 对象在异步回调中已失效(React 合成事件池回收),得提前用 event.persist() 或解构所需字段debounce 默认不执行 trailing 调用,要手动传 { trailing: true };而自己写的简易版默认就是 trailing,行为不一致容易误判touchmove 触发频率比 mousemove 高得多,同样 delay 值下更易卡顿,建议把 delay 设为 16ms(≈60fps)的整数倍