JavaScript代碼在執(zhí)行時并不完全是由上到下一行一行執(zhí)行的,由此產(chǎn)生了一個提升的問題。
什么是提升
可以簡單理解為:聲明(變量和函數(shù))都會被“移動”到各自作用域的最頂端,這個過程被稱為提升。
具體例子看提升
下面兩個例子a會log什么出來呢?
a = 233;
var a;
console.log(a)
console.log(a);
var a = 233;
“看起來”第一個例子應(yīng)該log出undefined,第二個例子變量a在使用前沒有先進行聲明,因此會拋出ReferenceError異常。但實際上,第一個例子a輸出233,第二個例子a輸出undefined。
為什么?
在知道為什么之前有必要了解:JavaScript雖然是解釋型語言,但在解釋JavaScript代碼之前首先對其進行編譯的。編譯階段中的一部分工作就是找到所有的聲明,并用合適的作用域?qū)⑺鼈冴P(guān)聯(lián)起來。
變量和函數(shù)在內(nèi)的所有聲明都會在任何代碼被執(zhí)行前首先被處理。
var a = 233;
上面的聲明看起來是一個聲明,而對于JavaScript而言實際上這是兩個聲明:var a;和a = 233;。第一個定義聲明是在編譯階段進行的。第二個賦值聲明會被留在原地等待執(zhí)行階段。
看回前面兩個例子,第一個例子的代碼會被這樣處理:
var a;
a = 233;
console.log(a);
第二個例子:
var a;
console.log(a);
a = 233;
注意:只有聲明本身會被提升,而賦值或其他運行邏輯會留在原地 。
函數(shù)聲明與函數(shù)表達式
第一個例子:
foo();
function foo() {
console.log(a); // undefined
var a = 233;
}
函數(shù)foo可以正常執(zhí)行。
第二個例子:
foo(); // TypeError
var foo = function bar() {
// ...
}
報錯:Uncaught TypeError: foo is not a function
由此可以看到:函數(shù)聲明會被提升,但是函數(shù)表達式卻不會被提升。
第三個例子:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};
這段代碼經(jīng)過提升后,可以理解為以下形式:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
};
由此可見:具名的函數(shù)表達式,名稱標(biāo)識符在賦值之前也無法在所在作用域中使用。
函數(shù)優(yōu)先
既然函數(shù)聲明和變量聲明都會被提升,那么問題來了,重復(fù)聲明的代碼中的優(yōu)先級是怎樣的,是函數(shù)聲明被提升還是變量聲明被提升?
繼續(xù)驗證:
foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
}
var foo盡管出現(xiàn)在 function foo()...的聲明之前,但它是重復(fù)的聲明而被忽略,因為函數(shù)聲明會被提升到普通變量之前。可以得知:函數(shù)聲明提升的優(yōu)先權(quán)大于普通變量聲明。
盡管重復(fù)的var聲明會被忽略掉,但出現(xiàn)在后面的函數(shù)聲明還是可以覆蓋前面的:
foo(); // 3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}
ES6中的變量聲明
ES6新增了let、const聲明,它們會存在一個暫時性死區(qū)(TDZ),表現(xiàn)出的情況會有所不同,具體可以參考:let, const
總結(jié)
- var變量聲明和函數(shù)聲明存在提升
- 函數(shù)表達式不會被提升
- 函數(shù)聲明提升的優(yōu)先權(quán)大于普通變量聲明
- let, const由于存在暫時性死區(qū),表現(xiàn)出的情況和var聲明有所不同