var 采用函数作用域与声明提升,声明前访问得 undefined;let 采用块级作用域与暂时性死区,声明前访问抛出 ReferenceError;for 循环中 var 共享单个绑定,let 每次迭代创建新绑定。

作用域和变量提升是理解 var 与 let 行为差异的两个关键入口——它们不是独立现象,而是编译阶段和作用域规则共同作用的结果。
var 的函数作用域 + 声明提升 = 提前可见但值未定
var 变量在编译阶段被提升到当前函数(或全局)作用域顶部,并初始化为 undefined。这意味着:
- 即使在声明语句之前访问,也不会报错,只会得到
undefined - 它不关心代码块(如
if、for),只认函数边界 - 多个
var声明同一变量名,后声明会覆盖前声明(不报错)
例如:
console.log(a); // undefined
var a = 1;
if (true) { var b = 2; }
console.log(b); // 2 —— b 被提升到函数/全局作用域,不是 if 块内私有
let 的块级作用域 + 暂时性死区 = 声明前不可触达
let 变量虽也被“提升”,但处于暂时性死区(TDZ):从进入块作用域开始,到 let 声明语句执行前,任何访问都会抛出 ReferenceError。
立即学习“Java免费学习笔记(深入)”;
- 作用域严格限制在
{}内(包括if、for、try等) - 不允许重复声明(同一块内
let x两次直接报错) - 不存在“提前读取得
undefined”这种模糊状态,强制声明优先
例如:
{
console.log(c); // ReferenceError: Cannot access 'c' before initialization
let c = 3;
}
if (true) { let d = 4; }
console.log(d); // ReferenceError: d is not defined
循环中体现最直观:闭包与迭代绑定
for 循环是检验两者差异的经典场景:
-
var i:整个循环共用一个i,所有回调函数共享最终值(如输出全是3) -
let i:每次迭代都创建一个新的绑定,每个回调捕获的是各自迭代的i(输出0、1、2)
这是因为 let 在 for 头部声明时,引擎为每次迭代生成独立的词法环境;而 var 声明只在函数作用域内做一次绑定。
本质是编译阶段处理方式不同
JS 引擎在编译阶段构建两个环境:
-
VariableEnvironment:存放
var和函数声明,var被初始化为undefined -
LexicalEnvironment:存放
let/const,仅注册绑定,不初始化——直到执行到声明行才赋值,此前均为 TDZ
所以不是“let 不提升”,而是“提升但不初始化”,这是设计上对可预测性和安全性的加强。


















