变量提升是一个将变量声明或者函数声明提升到作用域起始处的过程,即变量声明 var
和函数声明 function fun() {..}
在会发生变量提升过程。
但对于 ES2015 引入的 let
,变量提升是不能准确描述其变量初始化过程和可用性判断的,即 let
变量提升是不同寻常的。
ES2015 为 let
提供了一个不同的改进机制,它要求了更严格的变量声明方式(即在定义变量前是无法访问它的),从而在结果上保证了更好的代码质量。
在本篇博文中,我们一起深入了解这个过程的更多细节。
当引擎使用变量时,它们的生命周期包含以下阶段:
undefined
。一个变量在通过声明阶段后,它还是处于未初始化的状态,因为此时它仍为进入到初始化阶段。
注意,按照变量的生命周期过程,声明阶段与我们通常所说的变量声明是不同的术语。简单来讲,引擎处理变量声明需要经过完善的这 3 个阶段:声明阶段、初始化阶段和赋值阶段。
var
变量的生命周期稍微熟悉下变量的三大生命周期阶段,现在让我们用它们来描述引擎是如何处理 var
变量的。
假设一个场景,当 JavaScript 遇到了一个函数作用域,其中包含了 var variable
的语句,则在任何语句执行之前,这个变量就已经通过了声明阶段和初始化阶段(对于 var
来说,该两阶段不存在任何间隙)。
同时,var variable
在函数作用域中的位置并不会影响它的声明和初始化阶段的优先进行。
在声明和初始化阶段后,赋值阶段之前,变量的值为 undefined
,且已经可以被使用了。
在赋值阶段 varibale = 'some value'
,赋值语句使得变量得到新的赋值。
对于 var
,变量提升指 var
变量的声明阶段和初始化阶段得到提升,并且这两阶段之间没有任何的间隙。
下面是一个可供参考的范例:
function multiplyByTen(number) {
console.log(ten); // undefined
var ten;
ten = 10; // 赋值阶段
console.log(ten); // 10
return number * 10;
}
multiplyByTen(4); // 40
当 JavaScript 开始执行 multiplyByTen(4)
时进入到函数作用域中,变量 ten
在第一个语句之前就完成了声明和初始化阶段,并且值为 undefined
,故在调用 console.log(ten)
时打印为 undefined
。
语句 ten = 10
为变量赋于 10
,故在赋值后,console.log(ten)
打印了正确的 10
值。
对于 function
,声明、初始化和赋值阶段在封闭的函数作用域的开头,便立即执行,其提升优先级比 var
和 let
提升优先级高。 funName()
可以在作用域中的任意位置被调用,与其声明语句所在位置无关。
值得注意的是,函数声明会被提升,但是函数表达式不会被提升。
foo(); // 3
// 这里不出现 TypeError 的原因是:
// 重复的声明会被忽略,所以 var 提升时,不会把已有的 foo 初始化为 undefined
bar(); // ReferenceError
// 出现 Reference 的原因是:
// 具名函数表达式的函数名只在函数内部有效
function foo() {
console.log(1);
}
var foo = function bar() {
console.log(2);
}
function foo() {
console.log(3);
}
foo(); // 2
let
变量的生命周期let
的提升是只针对声明阶段的提升。
现在让我们研究这样一个场景,当解释器进入了一个包含 let variable
语句的块级作用域中,这个变量立即通过了声明阶段,并在作用域内注册了它的名称。
然后解释器继续解析块语句。
如果这时尝试访问 variabl
,JavaScript 将会抛出 ReferenceError: variable is not defined
,因为这个变量的状态依然是未初始化的。
在声明阶段结束到初始化阶段开始, variable
处于临时死区。
当解释器到达语句 let variable
时,此时变量通过了初始化阶段,现在变量状态为已初始化的,并且具有 undefined
的值,同时变量也离开了临时死区。
之后当到达赋值语句 variable = 'some value'
时,变量通过了赋值阶段。
如果 JavaScript 遇到了 let variable = 'some value'
,那么变量会在这一个条语句中完成初始化和赋值阶段。
至此,我们知道变量提升分为三种:
var
只有声明阶段和初始化阶段被提升。function
的声明阶段、初始化阶段和赋值阶段都被提升。let
只有声明阶段被提升。