let 声明循环变量可避免闭包陷阱,因其每次迭代创建独立块级绑定;var 因函数作用域共享变量,异步操作读取到最终值;该机制仅适用于 for/for...of/for...in,while 循环需手动处理作用域。

用 let 声明循环变量,就能避免 for 循环中常见的闭包陷阱——因为 let 为每次迭代创建独立的块级作用域绑定,而 var 只有一个函数作用域绑定。
为什么 var 在 for 中会出问题
使用 var 声明循环变量时,变量提升到函数顶部,整个循环共用同一个变量。当异步操作(如 setTimeout)在循环结束后执行时,读取到的是循环结束后的最终值。
例如:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(不是期望的 0, 1, 2)
let 如何解决这个问题
let 在每次循环迭代开始时,都会为该次迭代创建一个新的绑定(即新的“i”),每个回调函数捕获的是各自迭代中的那个独立变量值。
立即学习“Java免费学习笔记(深入)”;
- 每次迭代都有自己的块级作用域,
i不再被共享 - 箭头函数或普通函数内部访问的
i是当前轮次的值 - 不需要额外封装(比如 IIFE)也能自然解决
改写后:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2 ✅
注意:仅限 for / for...of / for...in,不适用于 while
let 的“每次迭代重新绑定”行为是 for 循环语法层面的特殊处理,只适用于 for (let ...)、for...of、for...in 这类明确有“迭代次数”概念的结构。
而 while 或手动递增的循环没有这个机制:
let j = 0;
while (j < 3) {
setTimeout(() => console.log(j), 100);
j++;
}
// 仍输出:3, 3, 3 —— 因为 j 是同一个变量
这种情况下,仍需手动制造作用域,比如用立即执行函数或在循环体内用 let 声明新变量:
let k = 0;
while (k < 3) {
let current = k; // 每次迭代新建一个绑定
setTimeout(() => console.log(current), 100);
k++;
}
// 输出:0, 1, 2 ✅
兼容性与实际建议
现代浏览器和 Node.js(v6+)都支持 let,构建工具(如 Babel)也能向下编译。但在必须支持老旧环境(如 IE)时,仍需用 IIFE 方式回退。
- 优先使用
for (let i = ...)替代var - 避免在循环内修改
let声明的变量名(如i++是允许的,但重赋值可能引发困惑) - 如果逻辑复杂,把异步操作抽成独立函数,显式传参更清晰


















