用 let 声明循环变量可解决定时器捕获错误索引问题;var 因变量提升和共享作用域导致所有定时器输出终值(如5),而 let 每次迭代创建独立绑定,使 setTimeout 正确捕获各次的 i 值(0至4)。

用 let 声明循环变量,就能让每次迭代拥有独立的块级作用域,从而让定时器捕获正确的索引值。
问题根源:var 声明导致变量提升和共享作用域
var 声明的变量会被提升到函数作用域顶部,整个 for 循环共用同一个 i 变量。当所有定时器异步执行时,循环早已结束,i 的值已是终值(如 5),所以全部输出 5。
- 例如:
for (var i = 0; i console.log(i), 100); }输出五个 5 - 本质是闭包捕获的是同一个变量的最终值,不是每次迭代的快照
解法一:用 let 替代 var(推荐)
let 在每次循环迭代中创建一个新的绑定,每个 setTimeout 捕获的是该次迭代独有的 i 值。
- 写法:
for (let i = 0; i console.log(i), 100); } - 输出:0、1、2、3、4(顺序可能因事件队列略有浮动,但值正确)
- 简洁、语义清晰,无需额外包装,ES6+ 环境直接可用
解法二:闭包立即执行函数(IE 兼容方案)
在 ES5 环境中,可把 i 作为参数传入 IIFE,形成独立作用域:
立即学习“Java免费学习笔记(深入)”;
- 写法:
for (var i = 0; i console.log(j), 100); })(i); } - 每次调用 IIFE 都把当前
i的值复制给形参j,确保定时器引用的是当时快照 - 兼容老浏览器,但代码略冗长,现代项目优先选
let
其他注意事项
避免误用 const 直接声明循环变量(会报错),也不要试图在 setTimeout 回调里用 let i = i 这类赋值——那只是新建同名变量,不解决捕获问题。
- 如果需在定时器中访问循环中的对象或数组项,同样建议用
let声明索引或解构出值,如let item = arr[i] - 箭头函数本身不改变作用域行为,关键在外部变量的声明方式
不复杂但容易忽略——一个 let 就能守住每次迭代的“现场”。


















