JavaScript的變數提升機制
變數提升
JavaScript的變數提升有兩種,用var
宣告的變數以及用function
宣告的變數。
用var宣告的變數
我們先來看下面這段程式碼,a的值是多少
程式碼1
console.log(a);
var a;
按照以往程式語言的思路來看,程式碼自上而下執行,按這種思路,會報錯,因為執行到第2行時,變數a還沒有定義,所以會報錯a is not defined
然而事實上答案是undefined
好,抱著疑惑,我們看下面的程式碼
var a;
console.log(a);
我們發現,這兩段程式碼是一樣的,那麼又有一個新的問題,是不是有沒有var a
都無所謂,它的答案始終是undefined
程式碼3
console.log(a);
好,它終於報錯了,所以這證明了javaScript
程式碼並不是自上而下執行的,至少從表面看上面是這樣的。
於是我們再看程式碼4
程式碼4
console.log(a);
var a = 2;
因為變數提升嘛,所以答案是2,然而事實上,它依然是undefined
,why?
這時候我們有請編譯器這位負責語法分析及程式碼生成等髒活累活的大佬。
編譯器在看到var a = 2;
,它會將其看做兩個宣告,var a;
和a = 2
,第一個宣告在編譯階段進行,第二個宣告會被原地等待執行階段。
也就是說上面程式碼,會變成下面的這段程式碼
var a;
console.log(a);
a = 2;
所以最終會是undefined
好,我在囉嗦一下,看這段程式碼5
程式碼5
a = 2;
var a;
console.log(a);
我想大家應該已經知道這段程式碼執行時的真正順序及其答案了,沒錯,答案是2,但我想說的是把第2行給註釋掉,答案依然是2,但這個和變數提升沒啥關係了,是嚴格模式與非嚴格模式的鍋,在非嚴格模式下允許開發者可以不使用宣告變數的關鍵字,但在嚴格模式下是不可以的,它會報錯的。
用function宣告的變數
與var
一樣,function
宣告的變數依然會提升。
log(5); function log(mes){ console.log(mes) }
按照之前的變數提升的理解,這段程式碼的真正順序是這樣的,
function log(mes){
console.log(mes)
}
log(5);
很好,很正確,那麼再看下一段程式碼
log(5);
var log = function(mes){
console.log(mes)
}
它報錯了,log is not a function
,從這裡我們可以看出,這種函式表示式是不會被提升的,只有函式宣告才會被提升,試著在最前面新增一行程式碼console.log(log)
,會先輸出undefined
。
所以這裡的真正順序是
var log;
log(); //這時候只是聲明瞭log這個變數,並不是函式,卻用函式的方法呼叫它,所以會報錯,說這不是一個函式。
log = function(mes){
console.log(mes)
}
在function裡用var宣告變數
我們雖然知道,var
宣告的變數會提升,但並不知道會提升到哪個程度。
在此之前來看一段程式碼
var a = 4;
function foo(){
var a = 5;
console.log(a);
}
foo();
console.log(a)
答案是5,4,先輸出5,再輸出4。
用var
宣告的變數是有函式作用域的,所以foo裡的a和foo外面的a沒有任何關係,這種情況正是我想要的。
再改下程式碼
function foo(){
a = 5
console.log(a);
var a;
}
foo();
console.log(a)
答案是5,a is not defined
第4行程式碼輸出5,第9行報錯。
這種情況就是變數提升只會提升到變數所在的 作用域的頂部,不會提升到父級作用域。
因此可以得出一個結論:變數提升只會將變數提升到自己所在的作用域的頂部
函式優先
既然用var
和function
的變數都有提升的功能,那如果同一個變數用這兩種都宣告會怎樣,好吧,看標題就知道了,函式優先。
具體看下程式碼
foo();
var foo;
function foo(){
console.log(1)
}
foo = function(){
console.log(2)
}
答案是1
這段程式碼其實這樣子的
function foo(){
console.log(1)
}
foo();// 1
foo = function(){
console.log(2)
}
仔細一看,var foo;
沒了,沒錯,它被引擎忽略了,認為重複宣告所以把它拋棄了。
好,既然var
宣告的變數比不了函式宣告,那就用函式宣告,多次宣告同個變數。
foo()
function foo(){
console.log(1);
}
foo()
function foo(){
console.log(2);
}
foo()
function foo(){
console.log(3);
}
foo()
foo
聲明瞭三次,呼叫了四次,每次呼叫的結果都是3,所以最後的函式宣告會覆蓋之前的函式宣告
但是var
還想掙扎一下,覺得還是有必要證明自己的存在感的。
foo()
function foo(){
console.log(1);
}
var foo;
foo()
foo = function(){
console.log(2);
}
foo()
function foo(){
console.log(3);
}
foo()
仔細看,中間那部分程式碼改了,依次輸出3,3,2,2
雖然var foo
被忽略了,但下面的函式還是有用的,這段程式碼可以看成是這樣的
function foo(){
console.log(3);
}
foo();//3
foo();//3
foo = function(){
console.log(2);
}
foo();//2
foo();//2
在普通塊內部宣告函式
之前是在作用域宣告函式,現在來塊裡面宣告函式
function foo(){
console.log(b); // undefined
b(); //TypeError: b is not a function
var a = true;
if(a){
function b(){
console.log(2)
}
//下面這段程式碼和上面的結果一樣
// var b = function(){
// console.log(2)
// }
}
//b() --> 這裡會被執行
}
foo()
從上面看上去,b是undefined
,證明這個變數還是有的,只不過它並不是一個函式,這情況和用函式表示式差不多。
總結
- 提升分為函式宣告提升和變數宣告提升
- 宣告變數用
var
,宣告函式用function
- 變數提升會將變數提升到自己所在作用域的頂部
- 函式表示式不存在提升的機制。
- 函式宣告和變數宣告同時宣告同一個識別符號時,函式宣告優先
- 多個函式宣告同一個識別符號時,最後一個宣告覆蓋先前的宣告