用let声明计数器使每次迭代拥有独立绑定,闭包捕获各自i值输出0、1、2;var则共享同一变量,输出3、3、3;复杂初始化如循环头定义函数会破坏let的独立性,导致异常输出。

循环体中用 var 还是 let 声明计数器,直接决定闭包捕获的是“同一个变量”还是“各自独立的值”。
var:所有闭包共享一个变量
var 声明的变量具有函数作用域,在整个函数内只存在一个绑定。循环中每次迭代都修改这同一个变量,后续闭包(比如 setTimeout 回调、数组里存的函数)执行时,读到的都是循环结束后的最终值。
- for (var i = 0; i console.log(i), 0); } → 输出 3, 3, 3
- 原因:三个 setTimeout 共享全局(或函数级)的 i,等异步执行时,i 已经变成 3
let:每次迭代创建独立绑定
let 在 for 循环头部声明时,JavaScript 引擎为每次迭代生成一个新的块级绑定。每个闭包捕获的是自己那轮迭代中的 i,互不干扰。
- for (let i = 0; i console.log(i), 0); } → 输出 0, 1, 2
- 原理不是“自动加闭包”,而是词法环境为每轮迭代保留了独立的 i 绑定记录
注意边界情况:复杂初始化会打破预期
如果 for 循环头部包含函数定义(如 getI = () => i),这些函数在初始作用域中创建,捕获的是最开始那个 i 的绑定,而不是每次迭代的新绑定。此时即使用了 let,也可能输出全 0。
- for (let i = 0, getI = () => i; getI() 0, 0, 0
- 因为 getI 是在循环开始前就定义并绑定了初始 i,后续 i 的更新不影响它
实际建议
- 常规 for 循环中需要闭包行为(如事件监听、异步回调、函数数组),优先用 let
- 避免在 for 头部同时声明变量和函数;若必须,把函数移到循环体内定义
- 兼容老环境需用 var 时,可用立即执行函数(IIFE)或参数传入方式模拟独立绑定











