You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么for循环使用let声明迭代器时每次迭代都会初始化新值?

for循环let迭代变量的闭包行为原理解析

复现代码如下:

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}
// 运行输出: 
// 0 
// 1
// 2
// 3
// 4

针对提出的两个疑问,核心原理如下:

1. let每轮迭代新建i的逻辑,和「同作用域不可重复声明」规则不存在冲突

这是ES6规范对for循环中let声明迭代变量的专属设计,完全符合let的基础规则:

  • 循环头部的let i = 0只会在循环启动时声明一次,属于循环外层的父块作用域,不存在重复声明问题
  • 每一轮循环体执行前,引擎会自动创建一个独立的子块级作用域,在这个子作用域里声明一个新的i变量,用来承接当前轮次的迭代值。不同轮次的i属于完全独立的作用域,互相之间不干扰,自然也不触发重复声明的校验
  • 每轮迭代结束执行i++时,引擎会自动把当前轮子作用域的i值加1,赋值给下一轮子作用域新建的i变量

而var声明没有这套特殊逻辑:var是函数级/全局级作用域声明,整个循环从头到尾只存在一个共享的i变量,所有setTimeout回调引用的都是这同一个变量。等1秒后定时器触发时,循环早就执行完毕,i的值已经累加到5,所以会连续输出5个5。

2. 闭包不会固定引用初始值为0的原始i

这里首先要纠正一个对闭包的常见认知偏差:闭包捕获的是变量本身的引用绑定,不是函数定义时刻变量的静态快照值。
产生“回调应该引用初始值0的i”这个误解,本质是误以为所有轮次的回调都绑定了循环外层父作用域里的初始i。实际上每一轮的setTimeout回调都是定义在当轮的独立子块作用域中,闭包绑定的是当前轮子作用域下的专属i变量:

  • 第1轮的回调绑定第1轮的i,值固定为0,后续迭代的赋值操作不会影响这个独立变量
  • 第2轮的回调绑定第2轮的i,值固定为1
  • 以此类推,5轮迭代一共生成5个独立的i变量,分别对应0-4的值,等定时器触发时,每个回调读取自己绑定的i,就会依次输出0、1、2、3、4

这套逻辑本质是把ES5时代开发者手动写IIFE传参隔离作用域、固定迭代值的操作,内置成了for循环let声明的默认行为,省去了手动写闭包的冗余代码。


内容的提问来源于stack exchange,提问作者John Jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 08:54:22