闭包在setTimeout中捕获的是外层变量的引用而非副本,故循环中用var会导致所有回调输出同一终值;改用let、IIFE或函数封装可为每次迭代创建独立作用域,确保正确输出对应值。

闭包在 setTimeout 中的执行表现,核心在于:函数体中引用了外层作用域的变量,而该变量在定时器真正执行时仍能被正确访问和使用——但若处理不当,容易出现“所有回调都输出同一个值”的典型问题。
为什么 setTimeout 中常遇到“输出都是最后一个值”?
这是因为 for 循环(尤其用 var 声明)本身不产生块级作用域,循环结束时变量已固定为终值;而 setTimeout 是异步延迟执行,等到它运行时,循环早已完成,闭包捕获到的其实是这个最终值。
例如:
<!-- 错误写法 -->
立即学习“Java免费学习笔记(深入)”;
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3如何让每个定时器拿到对应的 i 值?
本质是为每次迭代创建独立的作用域,使闭包捕获各自那一份变量。常用方式有:
-
用
let替代var:ES6 中let在每次循环中声明新绑定,每个回调闭包捕获的是不同轮次的i -
立即执行函数(IIFE)传参:把当前
i作为参数传入,形成局部变量供闭包引用 -
封装成独立函数并传参:将
setTimeout逻辑抽离为带参数的函数,调用时实参即刻绑定
示例(推荐 let 写法):
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2闭包真正保存的是什么?
不是变量的副本,而是对外层变量的引用。这意味着:如果外层变量后续被修改,闭包内读取到的就是修改后的值。
例如:
function createTimer() {
let count = 0;
setTimeout(() => console.log(count), 100);
count = 100;
}
createTimer(); // 输出:100这说明闭包持有对 count 的引用,而非定义时的快照。
实际开发中的注意事项
- 避免在循环中直接用
var+setTimeout,极易出错 - 若需在定时器中访问动态变化的数据,确认闭包捕获的是预期时刻的值(必要时用立即求值或解构赋值固化)
- 注意内存泄漏风险:闭包长期持有对外部大对象的引用,且定时器未清除,可能导致对象无法被回收
- 使用
clearTimeout配合闭包变量管理定时器生命周期,尤其在组件卸载、请求取消等场景










