防抖函数核心是每次触发清除前次定时器并重设,确保只执行最后一次;需用闭包隔离 timer、用 apply 保持 this 和参数,基础写法:function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; }

防抖函数怎么写?关键在 clearTimeout 和闭包
防抖的核心逻辑是:每次触发都重置定时器,只执行最后一次。常见错误是直接在全局声明 timer 变量,导致多个防抖实例互相干扰。
正确做法是用闭包保存每个实例独立的 timer:
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}-
fn必须能接收apply传入的参数,否则事件对象或输入值会丢失 - 如果需要立即执行首次调用(leading edge),得加判断逻辑,原生实现不带这个,默认是 trailing
- 注意
this指向——绑定在 DOM 元素上时,fn内部的this就是该元素;若需固定上下文,建议提前用fn.bind(context)
节流函数两种主流实现:时间戳 vs 定时器
节流要保证「单位时间内最多执行一次」,但两种写法行为不同:
✅ 时间戳版(首次立即触发):
立即学习“Java免费学习笔记(深入)”;
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);
}
};
}- 搜索框建议用防抖,滚动加载更多适合节流(尤其是时间戳版,用户一滚动就立刻触发请求)
- 定时器版在连续高频触发下,最后一次调用可能被“卡住”——比如用户快速拖拽 5 秒,最后一下松手后还要等
limit才执行 - React 中若在函数组件里定义节流函数,要注意不要每次渲染都新建,否则
useEffect依赖项变化会失效
实际项目中哪些地方必须加防抖/节流?
不是所有高频事件都要加,重点看是否引发副作用:
-
input事件做实时搜索 → 必须防抖,否则每打一个字都发请求 -
resize改变布局 → 推荐节流(如更新 canvas 尺寸、重排网格),避免重绘风暴 -
scroll实现吸顶或懒加载 → 节流更稳妥,防抖会导致滚动停止后才计算,体验卡顿 - 按钮重复点击提交 → 防抖 + 禁用按钮更可靠,单靠防抖不能阻止网络层重复请求
- Canvas 动画帧驱动(如
requestAnimationFrame)→ 不要用防抖/节流,它本身就是天然节流机制
容易被忽略的边界问题
真实业务里最常踩坑的不是写法,而是上下文和清理时机:
- Vue 组件或 React useEffect 中创建的防抖函数,组件卸载时没清除
setTimeout,会报Can't perform a React state update on an unmounted component - 防抖函数被当作
onClick处理器时,event对象在异步回调中已失效(React 合成事件池回收),得提前用event.persist()或解构所需字段 - Lodash 的
debounce默认不执行 trailing 调用,要手动传{ trailing: true };而自己写的简易版默认就是 trailing,行为不一致容易误判 - 移动端
touchmove触发频率比mousemove高得多,同样 delay 值下更易卡顿,建议把 delay 设为 16ms(≈60fps)的整数倍











