//使用var时:
for(var j=0; j<3; j++) {
setTimeout(() => console.log(j), 0); // 输出3 3 3
}
//使用let时:
for(let k=0; k<3; k++) {
setTimeout(() => console.log(k), 0); // 输出0 1 2
}
首先,var声明的变量是函数作用域或全局作用域的,而let是块级作用域的。在for循环中使用var时,变量j实际上会被提升到循环外部的函数或全局作用域中,因此整个循环中只有一个j变量。每次循环迭代时,j的值会被更新,而setTimeout中的回调函数是在循环结束后才执行的,此时j的值已经变成了3,所以三次回调都输出3。
而使用let时,每次循环都会创建一个新的块级作用域,变量k在每次迭代中都是一个新的绑定,即每个setTimeout回调捕获的是该次迭代时的k值。因此,当回调执行时,每个回调都有自己的k值,分别是0、1、2,所以输出0 1 2。
一、核心机制对比
特性 | var j 循环案例 | let k 循环案例 |
---|---|---|
作用域 | 全局/函数作用域 | 块级作用域(每次迭代独立作用域) |
变量存储 | 单次循环共用同一变量 | 每次迭代创建新变量副本 |
闭包捕获 | 所有回调共享最终值(3) | 每个回调捕获独立迭代值(0/1/2) |
输出结果 | 3 3 3 | 0 1 2 |
二、执行过程图解
1. var
循环(时间线模型)
主线程
├─ j=0 → 创建回调1(引用j)
├─ j=1 → 创建回调2(引用j)
├─ j=2 → 创建回调3(引用j)
├─ j=3 → 循环结束
└─ 事件循环触发 → 执行3次回调(j=3)
2. let
循环(内存快照)
迭代1:块作用域A → k=0 → 回调1绑定k=0
迭代2:块作用域B → k=1 → 回调2绑定k=1
迭代3:块作用域C → k=2 → 回调3绑定k=2
循环结束 → 执行回调时读取各自块级变量