在我们学习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引擎在编译阶段对标识符的预先处理机制。
也就是说在编译阶段有三个流程
- 扫描函数声明(整体提升)
- 扫描变量声明(仅声明提升)
- 按顺序处理函数参数和形参绑定 原始代码经提升后的等效形式:
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。