javascript 中闭包实现防抖函数的核心在于利用函数作用域保留上一次定时器状态。通过返回一个内部函数,将定时器 id 存储在外部函数的变量中,每次触发时清除旧定时器并设置新定时器,从而实现延迟执行且只执行最后一次调用的效果。

如何编写通用的防抖函数
编写通用防抖函数需要接收目标函数和等待时间作为参数,并返回一个新的控制函数。这个新函数在调用时会检查是否已有待执行的定时器,如果有则清除,然后重新设置定时器指向目标函数。
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}上述代码中,`debounce` 函数是外层函数,它定义了 `timer` 变量。返回的匿名函数是内层函数,它引用了 `timer`。当用户连续触发事件时,`clearTimeout` 会取消之前的计划,`setTimeout` 会安排新的执行,直到用户停止操作超过 `delay` 毫秒后,`fn` 才会真正执行。
如何处理立即执行与后续触发的冲突
在实际开发中,有时需要在第一次触发时立即执行,而后续的触发才进行延迟。这可以通过添加 `immediate` 参数来实现。在判断是否有定时器之前,先检查是否需要立即执行,如果需要且当前没有定时器,则直接调用原函数。
立即学习“Java免费学习笔记(深入)”;
function debounceImmediate(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const callNow = immediate && !timer;
if (timer) clearTimeout(timer);
if (callNow) {
fn.apply(this, args);
} else {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}在这个改进版本中,如果 `immediate` 为真且 `timer` 为空(即不是连续触发的中间状态),则立即执行 `fn`。随后无论是否立即执行,都会重置 `timer`。如果是连续触发,`callNow` 为假,进入 `else` 分支重置定时器。这种逻辑确保了首次点击立即响应,后续点击延迟响应。
为什么需要手动取消防抖
在某些场景下,如窗口调整大小或搜索输入,如果用户快速切换页面或组件卸载,未执行的定时器可能会在后台继续运行,导致内存泄漏或无效的状态更新。因此,防抖函数通常还需要提供一个 `cancel` 方法,允许外部主动清除定时器。
function debounceWithCancel(fn, delay) {
let timer = null;
const debounced = function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
debounced.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
};
return debounced;
}这里在返回的控制函数上挂载了一个 `cancel` 属性。当组件销毁或需要强制取消 pending 的任务时,调用 `debounced.cancel()` 即可清空定时器,确保资源被正确释放。


















