什么是变量提升?JavaScript 执行上下文是如何创建的?
简化版
变量提升是执行上下文创建阶段对声明进行预处理的结果。var 会被初始化为 undefined,函数声明会整体可用,let/const 也会被记录但处于暂时性死区,在初始化前访问会报错。
详细版
- 执行上下文通常分为创建阶段和执行阶段。
var声明会绑定到函数或全局作用域,并在创建阶段初始化为 undefined。- 函数声明会在创建阶段完成绑定,因此可以先调用后声明。
let/const/class有提升但未初始化,进入 TDZ 后在声明前访问会抛 ReferenceError。- 面试时要避免说“let/const 不提升”,更准确是“提升但处于暂时性死区”。
完整版教学
记忆钩子:提升不是代码真的搬家,而是执行前先建了一张变量登记表。
一、这题真正考什么
记忆钩子:提升不是代码真的搬家,而是执行前先建了一张变量登记表。
面试官想看的不是你能不能背一个术语,而是能不能解释 JavaScript 为什么会出现这些看似反直觉的结果。答题时要从执行模型、规范规则和工程边界三个角度展开。
二、底层机制怎么理解
JavaScript 运行一段代码前,会先创建执行上下文,建立变量环境和词法环境。声明被扫描并登记到对应环境中,随后代码才逐行执行。var 的特殊之处在于它创建时就被赋值为 undefined,而 let/const 在声明语句执行前不可访问。
JavaScript 高频题经常围绕“运行时如何决定结果”展开。只要能把创建阶段、调用方式、查找链路、转换规则或内存可达性说清楚,就能把题目从记忆题讲成原理题。
三、带数字或代码的小推演
代码 console.log(a); var a = 1; 会输出 undefined,因为创建阶段已有 a。代码 console.log(b); let b = 1; 会报错,因为 b 虽然已在词法环境中登记,但还没完成初始化。
下面把核心规则压成几行,复习时可以按顺序手推一遍:
创建阶段:收集声明 -> 建立环境记录 -> 初始化 var/function
执行阶段:逐行运行 -> 执行赋值 -> 进入/离开作用域
TDZ = 词法绑定存在,但初始化之前不可访问的时间段
四、执行流程怎么走
可以把这道题放到下面的运行流程里看:
进入全局或函数
|
创建执行上下文
|
扫描 var/function/let/const/class
|
初始化 var 和函数声明
|
开始逐行执行代码
|
执行到 let/const 声明时完成初始化
流程图的价值是把“现象”还原成“步骤”。面试追问通常就是换一段代码让你预测输出,按流程推比靠直觉猜更稳。
五、对比表格
| 声明方式 | 作用域 | 创建阶段表现 |
|---|---|---|
| var | 函数/全局作用域 | 初始化为 undefined |
| function 声明 | 函数/块级规则较复杂 | 函数体可用 |
| let | 块级作用域 | TDZ,声明前不可访问 |
| const | 块级作用域 | TDZ,且必须初始化 |
| class | 块级作用域 | TDZ,声明前不可访问 |
对比题要主动说适用场景和边界。JavaScript 的很多机制都不是“好或坏”,而是历史兼容、运行时灵活性和工程可维护性之间的折中。
六、工程实践和排查思路
- 不要把“提升”理解成源码被物理移动,否则很难解释 TDZ。
- 在同一作用域混用 var 和 let,容易出现重复声明、覆盖全局和循环闭包问题。
- 遇到输出预测题时,不要只报结果,要按创建、查找、调用、转换、执行顺序解释。
- 生产代码要优先选择可读性强、边界清晰的写法,把语言怪异点限制在工具函数或明确封装里。
七、常见误区与追问
- 误区:let 和 const 完全不会提升。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 误区:变量提升就是代码被移动到了顶部。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 误区:函数表达式和函数声明提升效果一样。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 追问:为什么 typeof 未声明变量和 TDZ 变量表现不同? 先给规则,再用一小段代码或内存/调用示意解释为什么。
- 追问:for 循环里的 let 为什么能解决闭包问题? 先给规则,再用一小段代码或内存/调用示意解释为什么。
- 追问:全局 var 和 window 属性有什么关系? 先给规则,再用一小段代码或内存/调用示意解释为什么。
八、加强记忆
按“先建上下文,再执行代码”记。var 是先登记并给 undefined,函数声明是先登记并给函数体,let/const/class 是先登记但锁在 TDZ 里。
复习 JavaScript 基础题时,可以固定按“创建阶段、执行阶段、查找链路、调用方式、类型转换、工程边界”六步组织答案。这个框架能覆盖大多数输出题和原理题。