如果你写了几年JS ,你一定对变量提升、暂时性死区(TDZ) 不陌生
但绝大多少人的认知,从根本上就是错的
❌ 误区1:let/const 没有变量提升,只有 var 才有提升
❌ 误区2:暂时性死区 = 没有提升,只是语法限制
❌ 误区3:变量提升是代码被“移到了顶部”
❌ 误区4:函数提升优先级永远高于变量提升
这些认知错位,平时写业务代码是可能察觉不到,但一旦遇到面试,复杂闭包,作用域嵌套bug,瞬间就会崩盘
今天这篇文章,彻底扒干净 JS 声明提升和 TDZ的底层真相,原因居然是这个,推翻全网 99%的错误解读,一次性根治所有相关bug 和面试盲点
一.破除核心误区:let/const真的没有提升吗?果真吗?
全网流传最广的一句话:
var 有提升,let/const没有提升
这句话是错的!大错特错!
精确真相: let/const完全存在声明提升,只是被暂时性死区锁住你,禁止访问
有人可能会说闹麻了,let/const不就是通过TDZ实现不声明提升的效果吗?表层效果和“无提升”一模一样:声明前访问直接报错,所以结果是没有提升。
非也,非也
这一段代码,可以直接击穿这个错误认知: 击穿这个错误认知:
var a = 100;
if (true) {
// 报错:Cannot access 'a' before initialization
console.log(a);
let a = 200;
}
我们来逻辑推演:
-
如果
let a真的没有提升:当前块作用域没有 a,引擎会向上层全局作用域查找,打印100,不会报错; -
现实是直接报错,唯一的解释:
let a 在进入块作用域的瞬间,就已经提升到当前作用域顶部了,并且被 TDZ 锁定,屏蔽了外层的全局变量 a。
这就是提升的铁证!
所谓的 TDZ,不是取消了提升,而是给提升加了一把锁。
二.彻底分清:无提升 VS 有提升+TDZ 的核心本质,你们知道吗?
这是99%程序员分不清的核心知识点,我用极简逻辑讲透:
1.真正的无提升(Java/Python/C等绝大多数语言)
引擎逐行执行,走到声明行,才会识别这个变量 声明之前:完全不认识这个变量,会向上层查找同名变量,无任何锁定效果
2.JS「let/const 提升 + TDZ 锁死」
预编译阶段,变量就已经提升到当前作用域顶部,引擎已经识别到了该变量。
只是在声明代码行执行前,变量处于暂时性死区:
✅ 变量已注册(提升完成)
✅ 屏蔽外层同名变量
❌ 禁止任何读取、访问操作
3. JS 「var 提升」
提升后挂载到 VO 变量对象,赋值 undefined,无死区限制,声明前可以正常访问。
三.提升不是“代码被移到顶部”
新手教程最爱说:变量、函数会被提升,移动到代码最顶部。
这是通俗错误解读,底层根本不是这样!
精准底层真相:
提升是 JS 预编译阶段的「变量登记机制」,不是代码位置移动。
JS 执行分两个阶段:
阶段1:预编译(瞬间完成)
引擎扫描当前作用域所有声明:
-
扫描所有
var变量,登记到 VO/AO,赋值undefined; -
扫描所有
function函数声明,登记到 VO/AO,赋值完整函数体; -
扫描
let/const,完成提升,进入 TDZ 锁定状态。
阶段2:逐行执行
代码位置完全不变,只是引擎提前认识了所有变量。
如果是代码真的“被移动到顶部”,会产生无数逻辑bug,JS 引擎从来不会修改你的代码位置。
四.全网最易混: var、let、function 提升优先级终极规则
很多人背的“函数提升优先于变量提升”是半截真理,不完整,极易踩坑。
完整、可落地、100%准确的提升顺序(预编译固定流程):
1.变量提升(var/let/const全部先登记)
2.函数声明提升(后覆盖)
实战经典代码:
console.log(a);
var a = 10;
function a() {}
执行结果:[Function: a]
推演过程:
-
预编译第一步:扫描 var 变量,VO.a =
undefined; -
预编译第二步:扫描函数声明,VO.a = 覆盖为完整函数;
-
执行阶段:打印 a,得到函数;
-
执行
var a = 10,a 被赋值为 10。
再看 let 场景(彻底击穿误区):
console.log(b);
let b = 20;
function b() {}
结果:直接报错
原因:
-
let b先提升,进入 TDZ 死区; -
后续函数声明无法覆盖死区变量;
-
声明前访问,触发 TDZ 报错。
这也再次证明:let 绝对有提升,否则函数提升会生效,不会报错。
五、暂时性死区(TDZ)的真正设计目的
很多人不知道,TDZ 是 ES6 为了修复 var 的历史缺陷,被迫新增的机制。
我们复盘 JS 发展史,瞬间通透:
-
JS 初期为了兼容新手,设计了变量提升,允许先使用后声明,降低入门难度;
-
但 var 无块级作用域、无死区,提升后提前访问得到
undefined,产生大量隐秘bug; -
ES6 不能直接废除提升(会导致所有老项目报错);
-
最终折中方案:
保留 let/const 的提升机制(兼容底层逻辑),新增 TDZ 暂时性死区,强制「先声明、后使用」,规范编码规范。
TDZ 核心作用总结:
✅ 解决 var 变量提升带来的诡异 undefined 问题
✅ 杜绝变量在声明前被使用,代码逻辑更严谨
✅ 屏蔽外层同名变量,解决作用域变量污染问题
✅ 配合块级作用域,彻底解决 var 的所有历史弊端
所以在使用变量时,一般都是const>let>var ,没有特殊情况,千万不要用var!
六、终极对比表(一张表吃透所有差异)
| 声明方式 | 是否提升 | 是否有TDZ | 声明前访问结果 | 作用域 |
|---|---|---|---|---|
| var | ✅ 提升 | ❌ 无 | undefined | 函数/全局作用域 |
| let | ✅ 提升 | ✅ 有 | 报错(死区锁定) | 块级作用域 |
| const | ✅ 提升 | ✅ 有 | 报错(死区锁定) | 块级作用域 |
| function | ✅ 优先提升 | ❌ 无 | 可直接调用 | 对应作用域 |
七、全文核心总结(理解大于记忆)
-
最大误区纠正:let/const 绝对有声明提升,只是被 TDZ 锁住,并非无提升;
-
TDZ 本质:提升后的变量临时锁定、禁止访问,声明行执行后解锁;
-
提升本质:预编译阶段的变量登记机制,不是代码位置移动;
-
提升优先级:变量先登记,函数后覆盖,let 死区变量不可被覆盖;
-
设计初衷:TDZ 是 ES6 为兼容历史、修复 var 漏洞的最优但也是无奈的折中方案;
-
核心区别:无提升会向外找变量,有提升+TDZ会屏蔽外层变量并报错。
写在最后
很多人学 JS 只会记表层效果,不懂底层原理,所以永远踩坑。
声明提升和 TDZ,看似简单,却是 JS 作用域、预编译、闭包的底层基石。
吃透这两个知识点,你对 JS 执行机制的理解,已经超过99%的普通开发者。
觉得本文干货硬核,恳请点赞 + 收藏 + 转发,顺手点个关注不迷路,下篇硬核剖析声明提升 JS 为什么非要设计出「声明提升」这种反直觉特性?
别的语言都没有,为什么唯独 JS 要有?
从 10 天仓促诞生,到 ES6 用 TDZ 补丁救场,背后藏着怎样的历史渊源和语言设计哲学?
敬请期待吧!!!