如何解决下面的循环输出问题?
面试速答(30 秒版 TL;DR)
- 经典题:
for (var i...) setTimeout(...)最终输出都是同一个值(通常是循环结束后的i)。 - 原因:
var是函数作用域,循环里共享同一个i绑定;定时器回调是异步,执行时循环早结束。 - 最佳解:用
let(块级作用域)为每次迭代创建新的绑定;或用 IIFE/函数参数把值“传进去”形成闭包隔离。
题目复现
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 常见输出:3 3 3
为什么会这样?
var i只有一个,循环每次只是把同一个i改掉。setTimeout回调会在本轮同步代码跑完后才执行。- 回调执行时,
i已经是3了,所以每次都打印3。
解法 1(推荐):用 let 让每次迭代有独立绑定
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0 1 2
面试补充点:let 在 for 循环中会为每次迭代创建新的词法环境绑定,因此回调闭包各自捕获不同的 i。
解法 2:IIFE 把当前值作为参数传入(闭包隔离)
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 0);
})(i);
}
// 0 1 2
这里回调闭包用的是 j(每次 IIFE 调用都会生成新的 j 绑定)。
解法 3:利用 setTimeout 的参数(把值“传给回调”)
for (var i = 0; i < 3; i++) {
setTimeout(
(j) => {
console.log(j);
},
0,
i
);
}
// 0 1 2
这个方案不依赖闭包隔离 i,而是让 setTimeout 在触发回调时把参数带进去。
典型追问:如果我要每秒输出一次 0、1、2 呢?
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), i * 1000);
}
易错点/坑
- 只会背“用 let”但说不清原因:要能讲清
var作用域、异步时机、以及let for的迭代环境。 - 试图用“复制变量值”解释闭包:更严谨的说法是“捕获的是绑定/环境引用”,
let让绑定变成每次迭代独立。
速记要点(可背诵)
var + 异步回调:共享一个i,回调执行时i已变成最终值。- 解法:
let或 IIFE 参数,核心是让每次迭代有独立的绑定。