探索JavaScript中的声明提升机制

379 阅读3分钟

在我们学习JS的过程中,我们会发现这么一个现象

console.log(a); //undefined
var a = 10;

明明变量a没有被声明,那么这段代码为什么不报错还能打印出结果?此现象源于JavaScript的变量提升(Variable Hoisting)机制。要理解其原理,需追溯var的设计背景。

var的来历

var是在JavaScript最初就存在的关键字,而JavaScript是为了快速开发而设计出的一门弱类型语言。等等,什么是弱类型的语言,简而言之就是会自动识别出变量的类型。var的作用域是函数作用域。

变量提升

在 JavaScript 引擎编译阶段,会创建执行上下文(Execution Context),其中包含变量环境(Variable Environment)和词法环境(Lexical Environment)。提升(Hoisting)的本质是JS引擎在编译阶段对标识符的预先处理机制。

deepseek_mermaid_20250510_fa125b.png
也就是说在编译阶段有三个流程

  1. 扫描函数声明(整体提升)
  2. 扫描变量声明(仅声明提升)
  3. 按顺序处理函数参数和形参绑定 原始代码经提升后的等效形式:
var a;  //声明提升
console.log(a); //undefined
a = 10;

let/const 的 TDZ 机制

在ES6之后,有出现了let 和 const 两种关键字,我们大胆猜想一下?既然var有变量提升,那么let和const会不会也会出现变量提升这种现象?答案是不会,请看代码

console.log(a); 
let a = 10;

输出: Uncaught ReferenceError: Cannot access 'a' before initialization 浏览器提示代码错误,变量a没有进行初始化!

console.log(a); 
const a = 10;

const声明的变量也会出现和上面一样的报错。关于let和const这种为声明变量就访问而出现的报错的这种现象JS也有一个有趣的名字:TDZ。
什么是TDZ?
TDZ(Temporal Dead Zone,暂时性死区)  是 JavaScript 中 let/const 声明特有的机制,指从 进入作用域 到 变量初始化完成 之间的区域,在此区域内访问变量会触发 ReferenceError
简单来说就是:变量在声明之前无法被访问,当然 var声明的变量除外。

函数提升

下面再来看这段代码

fun();
function fun() {
    console.log('你好啊');
}

这段代码经过提升之后长这个样子

function fun() {    // 函数声明整体提升到作用域顶端
    console.log('你好啊');
}
fun()

哦!函数也会像var声明的变量一样,在函数声明之前就可以调用。先别急,我们在看看函数的另一种写法

fun();
fun = function () {
    console.log('你好啊');
}

输出:Uncaught ReferenceError: fun is not defined
好奇怪,怎么报错了,函数不是也有提升机制吗? 先让我们仔细分析一下提升之后的代码

var fun;            // 变量声明提升,初始值为 undefined

fun();              
fun = function() {  
  console.log('你好啊');
};

fun确实是提升了,不过我们仔细瞧瞧,fun是一个变量,而不是一个函数,我们将一个变量当作函数来调用当然会报错了。

混合场景

如果我们将变量提升和函数提升放在一起会发生什么事情?
案例一

fun();
function fun() {
    console.log('你好啊');
}
console.log(a)
var a = 10;

浏览器会正常输出结果。
那如果我们再将代码的顺序替换一下那?
案例二

console.log(a)
var a = 10;
fun();
function fun() {
    console.log('你好啊');
}

浏览器依旧会正常输出结果。
上面的两段代码经过编译阶段之后实际是这样的

//案例一
fun();
function fun() {
    console.log('你好啊');
}
var a;
console.log(a)
a = 10;
//案例二
fun();
var a;
console.log(a)
a = 10;
function fun() {
    console.log('你好啊');
}

总结

  • var:存在变量提升,在编译阶段会被初始化为undefined

  • let: 存在TDZ,声明前不可访问,声明后未显式赋值则为undefined

  • const: 存在TDZ,声明时必须初始化,否则语法错误。

  • function:存在变量提升,在变量声明之前调用。

建议

在现在的开发方式中,var关键字一般不使用了,变量声明默认使用const,如果有变量赋值再修改为let。