var、let、const 的差异本质是存储位置不同:var 存于变量环境,有提升、无块级作用域、可重复声明;let/const 存于词法环境,有tdz、块级作用域、禁重声明;函数声明双重写入,但块级行为不一致。

从函数执行上下文最底层的存储架构出发,JS 中 var、let、const 的差异本质不是语法糖,而是它们被分别存放在执行上下文的两个独立区域:变量环境(Variable Environment)和词法环境(Lexical Environment)。这个物理隔离直接决定了它们在提升、作用域、重复声明、暂时性死区等行为上的全部坑点。
var:只进变量环境,无块级、有提升、可重复
var 声明全部进入变量环境(Variable Environment),该区域在编译阶段统一处理:
- 声明会被提升,值初始化为
undefined,但不会初始化真实值 - 不支持块级作用域——
{}内声明的var仍属于当前函数或全局上下文 - 允许同名重复声明(第二次声明被忽略,不报错)
- 函数内未声明就赋值(如
a = 1)会自动挂到变量环境对应的对象(如AO或VO)上,等效于var a = 1
let/const:只进词法环境,块级绑定、有TDZ、禁重声明
let 和 const 不进变量环境,只登记在词法环境(Lexical Environment)中。词法环境以栈结构嵌套管理,每个 {} 都可能生成新层级:
- 编译阶段仅“登记”绑定,不初始化值——访问即触发暂时性死区(TDZ),报
ReferenceError - 块级作用域严格生效:
if、for、try等花括号内声明,外部不可见 - 禁止重复声明(同一作用域下,哪怕一个是
let一个是const,也报错) -
const要求声明时必须赋值,且绑定不可重新赋值(注意:对象/数组内容可变,只是绑定地址不可变)
函数声明:双重身份,优先覆盖变量,但不跨块
函数声明(function foo() {...})是唯一同时写入变量环境和词法环境的声明方式:
- 编译阶段,函数体直接写入变量环境,优先级高于同名
var;执行阶段的var赋值无法覆盖它 - 但它不享受块级作用域——ES6 规范明确:块级函数声明在严格模式下是词法环境的一部分,但实际引擎(如 V8)大多将其提升至外层函数作用域,导致行为不一致
- 非严格模式下,块内函数声明可能被提升到函数作用域顶部;严格模式下部分引擎会限制其作用域,但仍有兼容性风险
- 箭头函数、函数表达式(
const foo = function() {})按const/let规则走,不参与函数提升
跨上下文陷阱:闭包与变量环境快照
当函数作为返回值或回调被保存时,它携带的是创建时所在执行上下文的词法环境引用,但变量环境中的 var 绑定会随上下文销毁而失效——不过由于闭包捕获的是整个词法环境链,而 var 实际也存在于该链的某一级中,所以依然可访问。真正危险的是:
- 循环中用
var声明迭代变量 + 异步回调(如setTimeout),所有回调共享同一个变量环境中的那个var绑定,最终输出全是最后一次值 - 用
let则每次迭代新建词法环境绑定,每个回调捕获各自独立的绑定,输出符合直觉 - 这种差异不是“作用域不同”,而是变量环境(单个可变槽) vs 词法环境(每次迭代新建栈帧)的底层存储模型差异所致











