闭包使 setTimeout 能持久化访问外部变量,解决循环中变量取值错误问题:var 共享变量导致输出重复值,而 IIFE 或 let 可创建独立闭包捕获每次迭代值,且闭包可维持任意状态,但需注意内存泄漏风险。

在 JavaScript 中,闭包让 setTimeout 内部能“记住”并持续访问外部函数作用域中的变量,从而实现变量的持久化——这不是靠全局变量,而是靠函数作用域链的自然保留。
为什么 setTimeout 里常出现变量“取不到最新值”?
典型问题:循环中为每个定时器设置延时执行,但所有回调都输出同一个最终值。这是因为 var 声明的变量被共享,且回调执行时循环早已结束,变量只留下最后一次赋值的结果。
例如:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
根本原因:所有定时器回调共享同一个 i,而 setTimeout 是异步延迟执行,等到真正运行时,循环已完成,i 已变为 3。
立即学习“Java免费学习笔记(深入)”;
用闭包捕获每次迭代的变量值
闭包的核心是:内部函数保留对外部函数作用域中变量的引用。我们可借助立即执行函数(IIFE)或块级作用域,让每次循环都创建独立的闭包环境。
-
IIFE 方式(ES5 兼容):把当前
i作为参数传入,形成独立作用域
for (var i = 0; i < 3; i++) {
(function (current) {
setTimeout(() => console.log(current), 100);
})(i);
}
-
let 声明(推荐,ES6+):
let在每次循环中创建新绑定,每个回调自动闭包捕获各自的i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 0, 1, 2
}
这里 let i 每次迭代都生成一个新绑定,setTimeout 回调形成的闭包各自引用不同的 i 绑定,变量自然持久化。
闭包持久化的不只是数字,还有任意数据和状态
闭包不仅能保存原始值,还能维持对象、函数、甚至私有状态。比如封装一个带计数器的延时任务:
function createDelayedLogger(prefix) {
let count = 0;
return function () {
count++;
setTimeout(() => {
console.log(`${prefix}: ${count}`);
}, 100);
};
}
const loggerA = createDelayedLogger('Task A');
const loggerB = createDelayedLogger('Task B');
loggerA(); // Task A: 1
loggerA(); // Task A: 2
loggerB(); // Task B: 1
每个 createDelayedLogger 调用都生成独立闭包,count 和 prefix 在定时器触发时依然可用——这就是闭包对变量的持久化能力。
注意内存与意外持有
闭包让变量“活”得更久,但也可能造成意料之外的内存占用:
- 如果闭包引用了大型 DOM 元素或数据结构,且定时器长期未执行或未清除,这些对象无法被垃圾回收
- 避免在闭包中无必要地引用整个外部对象,可只提取所需字段
- 使用
clearTimeout及时清理不再需要的定时器,解除闭包对上下文的隐式持有
例如,保存 setTimeout 返回的 ID,并在合适时机清除:
function delayedAction() {
const id = setTimeout(() => console.log('done'), 5000);
// ...后续可调用 clearTimeout(id) 提前释放


















