JavaScript 中的一切声明 ( var
, let
, const
, function
, function*
, class
) 均存在变量提升 (hoisted)。
下面的例子中,变量 a 的声明被隐式提升到作用域最顶端并被初始化为 undefined
, a
并未抛出异常,而未声明的变量 b
抛出 ReferenceError
异常。
console.log(a); // undefined
var a = 1;
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
var
/ function
/ function*
与 let
/ const
/ class
的不同在于是否初始化。
var
、 let
和 const
的「创建」过程都被提升了,但是 let
和 const
并没有初始化, var
被初始化为 undefined
。
因此访问 var
声明的变量时,不会报 ReferenceError
异常,而使用 let
, const
, class
声明的变量,被提升后不会被初始化,这些变量所处的状态被称为暂时性死区 (Temporal Dead Zone, TDZ),此时如果访问这些变量会抛出 ReferenceError
异常,看上去就像没被提升一样。
x = y = "global";
(function() {
x; // undefined
y; // Reference error: y is not defined
var x = "local";
let y = "local";
}());
函数声明提升
定义函数 声明式
声明式会导致函数提升,function 会被解释器优先编译。即我们用声明式写函数,可以在任何区域声明,不会影响我们调用
函数声明会将声明和赋值都提前,也就是整个函数体都会被提升到作用域顶部:
s(); // 1
function s() {
console.log(1);
}
定义匿名函数 函数表达式
函数表达式中的 function 则不会出现函数提升(但是赋予的变量会提升)。经由 JS 解释器逐行解释,到了这一句才会赋值函数表达式。因此如果调用在函数表达式之前,则会调用失败。
console.log(s); // undefined
var s = function s() {
console.log(1);
}
console.log(s); // f s(){console.log(1);}
对于上述定义方式调用 s() 并无区别,但是定义函数会存在函数体的提升,而定义匿名函数时只会将定义变量提升,赋值部分不会提升。
优先级
var s = function() {
console.log(0);
}
function s() {
console.log(1);
}
s(); // 0
在 JS 引擎中的执行优先级是 变量声明-->函数声明-->变量赋值
var s; // 变量声明
function s() { //函数声明
console.log(1);
}
s = function() { // 变量赋值
console.log(0);
}
s(); // 0