闭包是实现防抖和节流的核心机制,通过封装timer或时间戳等状态变量确保多次调用间状态隔离与正确维护,避免全局变量冲突,支持多实例独立运行,并需注意this绑定和参数传递。

闭包是实现防抖和节流的核心机制,它让函数能“记住”并持续访问其定义时的作用域变量(比如定时器 ID、上一次执行时间等),从而在多次调用中维持状态。
防抖(Debounce):用闭包保存 timer 引用
防抖要求在连续触发中只执行最后一次。关键在于每次新触发时清除前一次的定时器——这就需要把 timer 变量封闭在闭包内,不让外部干扰,又能让每次调用的函数体访问到它。
典型实现:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 使用
const handleResize = debounce(() => console.log('窗口调整完毕'), 300);
window.addEventListener('resize', handleResize);
说明:
• timer 是闭包变量,每次调用返回的函数都共享同一个 timer;
• 每次 resize 触发,先清掉旧定时器,再设新定时器;
• 延迟期间无新触发,fn 才真正执行。
节流(Throttle):用闭包维护时间戳或锁状态
节流限制函数单位时间内最多执行一次。常见两种策略:基于时间戳(立即执行)或基于定时器(延迟执行),都依赖闭包保存上次执行时间或锁定状态。
立即学习“Java免费学习笔记(深入)”;
时间戳版(首次立即执行):
function throttle(fn, limit) {
let lastExecTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastExecTime >= limit) {
fn.apply(this, args);
lastExecTime = now;
}
};
}
定时器版(平滑控制,避免卡顿):
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
说明:
• 两个版本都靠闭包把 lastExecTime 或 timer 封装起来,确保状态跨多次调用不丢失;
• 时间戳版响应快但可能在密集触发开头就执行;定时器版更均匀,适合 scroll/mousemove 等高频场景。
为什么必须用闭包?不用会怎样?
如果 timer 或 lastExecTime 写成全局变量:
• 多个 debounce/throttle 实例会互相覆盖,导致行为错乱;
• 不同函数共用同一 timer,一个被清除会影响另一个;
• 无法复用:你不能同时对 input 输入和按钮点击分别做独立防抖。
闭包提供了天然的“实例隔离”:
• 每次调用 debounce(fn, ms) 都创建独立作用域;
• 返回的函数携带专属 timer,彼此完全无关;
• 这正是高阶函数 + 闭包的经典协作模式。
进阶注意:this 和参数的正确传递
实际使用中容易忽略上下文丢失问题。闭包内部调用 fn 时需确保 this 和参数准确:
- 用 fn.apply(this, args) 或 fn.call(this, ...args) 绑定原始调用上下文;
- 用 ...args 收集所有参数,兼容任意形参函数;
- 若需取消防抖/节流,可暴露清理方法(需闭包暴露接口,例如返回含 cancel 属性的对象)。
示例增强版(支持取消):
function debounce(fn, delay) {
let timer = null;
const debounced = function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
debounced.cancel = () => clearTimeout(timer);
return debounced;
}


















