今天看到for代码,突然想仔细的看下
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出 0, 1, 2, 3, 4
}, 1000);
}
上面的输出,背都背会了,解释无非是let具有块级作用域,每次循环都会创建一个新的 i 绑定(即每个 i 是独立的)
,然后我就想啊,i
每次都独立了,为啥还能++,每次都是独立的,按道理是相互不影响的哇,于是我就查了下资料,真的是知识盲区。。。
1. for (let i=...)
的底层实现
实际上,引擎会为这种循环创建 **两个独立的 i
**:
- **外层
i
**:用于控制循环条件(i < 5
)和递增(i++
) - 内层
i
:每次迭代时,将外层i
的当前值 拷贝 到一个新的块级作用域变量中
用伪代码表示就是:
// 伪代码:引擎实际处理方式
{
let outer_i = 0; // 外层的控制变量
while (outer_i < 5) {
let inner_i = outer_i; // 每次迭代创建新的块级变量
setTimeout(() => console.log(inner_i), 1000);
outer_i++; // 递增的是外层的控制变量
}
}
2. 为什么需要这种设计?
- 目的1:保证每次迭代的
i
在块级作用域内独立(解决闭包问题) - 目的2:同时维护一个外部的计数器来控制循环流程
3. 关键结论
- **
i++
操作的是外层的隐藏变量**(不是全局变量,而是循环控制层的变量) - 每次迭代的
i
是外层变量值的快照,所以回调函数捕获的是独立值 - 这既保持了
let
的块级作用域特性,又实现了循环的正常递增逻辑
4. 对比实验
可以通过这个代码验证:
for (let i = 0; i < 3; i++) {
let i = 10; // 内层可以重复声明,证明循环头的i和外层不同
console.log(i); // 输出10,10,10
}
这个现象证明:循环头的 i
和循环体的 i
确实处在不同作用域。
(~ ̄▽ ̄)~