JavaScript中var声明的循环变量在异步操作中输出最后一个值,因var函数作用域共享i引用;用IIFE传参或let块级作用域可解决,推荐let。

在 JavaScript 中,用 var 声明的循环变量 i 在异步操作(如 setTimeout、fetch)中常出现“所有回调都输出最后一个 i 值”的问题。本质是闭包捕获的是变量的**引用**,而非当前轮次的**值**。解决核心在于:让每次迭代拥有独立的作用域和绑定的 i 值。
问题重现:var + setTimeout 的典型陷阱
以下代码本意是 0~2 依次输出,实际却输出三次 3:
<script>
for (var i = 0; i setTimeout(() => console.log(i), 100);
}
</script>
原因:var 是函数作用域,整个循环共用一个 i;所有 setTimeout 回调共享同一个 i 引用;循环结束时 i === 3,回调执行时自然都读到 3。
方案一:IIFE(立即执行函数表达式)手动创建作用域
通过自执行函数,把当轮的 i 作为参数传入,形成独立闭包:
<script>
for (var i = 0; i (function (currentI) {
setTimeout(() => console.log(currentI), 100);
})(i);
}
</script>
关键点:
• 每次迭代调用 IIFE,currentI 是形参,接收当轮 i 的**值拷贝**;
• 回调函数闭包捕获的是 currentI,它在每次调用中互不干扰;
• 兼容老浏览器,但语法稍冗长。
方案二:let 声明 —— 更简洁、更语义化的现代解法
let 具有块级作用域,每次 for 迭代都会为 i 创建一个**新的绑定**:
<script>
for (let i = 0; i setTimeout(() => console.log(i), 100);
}
</script>
效果等同于 IIFE,但更直观:
• 每轮循环的 i 是独立变量,不是共享引用;
• 回调中的 i 指向各自迭代块内的绑定;
• 无需额外包装,代码干净,是目前推荐写法。
补充:async/await 场景下同样适用
即使使用 async 函数,let 依然可靠:
for (let i = 0; i setTimeout(async () => {
const res = await fetch(`/api/${i}`);
console.log(i, res.status); // i 正确对应本轮
}, 100);
}
注意:
• 不要用 var 或在 async 外层用 let 却在内部修改(如 i++),会破坏绑定;
• 若需在循环内复用变量名但又想保留原始 i,可显式重命名:for (let i = 0; i 。

















