或許你在編寫JavaScript時,會注意到這麼一個現象,聲明的出現與其作用域內的操作位置有微妙的關係,
有人認爲JavaScript引擎執行代碼是從上到下一行一行執行的。實際上這不全對
例如:
a=2;
var a;
console.log(a);
如果按照JavaScript引擎是一行一行代碼解釋的話,先賦值再聲明再輸出,因爲var a在a=2後面,理所當然認爲a被重新賦值了,那麼輸出結果是undefined,但是很遺憾並不是,輸出結果爲2。
我們將它們之間的位置調換下會得到:
console.log(a);
var a;
a=2;
如果按照對之前代碼進行的分析,那麼此處會輸出 "2" 或者這樣思考,a未定義前就使用,那麼此處會輸出"ReferenceError"。 但是結果卻是 "undefined" ,爲什麼???
這其實就是“先有雞還是先有蛋的問題”即先聲明在使用還是先使用在聲明的問題
1.2編譯器
編譯器在JavaScript中的作用是進行詞法分析/語法分析以及生成代碼。編譯的一部分工作----在詞法分析中產生詞法域,這時候會得到詞法域以及各類聲明。
包括函數和變量在內的所有聲明在任何代碼被執行之前首先被處理
當看到"var a=2"時你以爲這會是一句聲明語句,其實不然,這在JavaScript看來是兩種聲明,一種是聲明語句"var a;",另一種是賦值語句"a=2;",聲明語句在編譯時就已經完成,而賦值語句則要在原地等待執行。
之前我們的代碼它的執行過程如下
//先編譯再執行
a=2; //等待執行
var a; //編譯時先執行此聲明
console.log(a);
如上所示,因爲編譯器首先是編譯此代碼隨後再執行,所以先執行 var a ;在執行 a=2 ;最後執行 console.log(a);所以輸出爲2。
如果按照從上到下的執行順序,那麼上面代碼可變爲
var a; //先執行聲明語句
a=2;
console.log(a);
那麼,之前講的這個代碼片段
console.log(a);
var a=2;
可變爲
var a;
console.log(a);
a=2;
在這裏JavaScript將 var a=2 分解爲 var a 和 a=2。var a 首先被執行,所以上升到第一位。
--------------------------------在上面例子中,var a 聲明語句總是被提升到第一位,所以叫提升
提升過程有兩點需要注意的
①函數聲明也是可以提升的,
例如
foo(); //foo()函數能夠正常運行
function foo(){ //函數聲明發生了提升
console.log(a); //ReferenceError
var a=2;
}
函數聲明提升,所以第一行調用可以正常執行。
②提升只會提升聲明,而不會提升邏輯(賦值等操作)
如上面的 var a 被提升(是在foo(...)函數中的最上方,而不是整個程序),剩下 a=2 在原地。
這時候我們稍加對上面程序進行修改,你就會發現問題
foo(); //TypeError而不是ReferenceError
var foo=function bar(){ //函數聲明發生了提升
console.log(a); //ReferenceError
var a=2;
}
var foo=function bar(){...} 中同樣被分成兩部分 var foo 以及 foo=function bar(){....},聲明被提升到最頂部,賦值操作沒有提升。
因此foo()調用沒有發生ReferenceError錯誤。但是foo()並沒有被賦值(如果它是一個函數聲明而不是一個函數表達式,那麼它就必須要賦值,此處它沒有賦值,所以默認的值爲undefined),foo()由於對undefined進行函數調用而導致非法操作,拋出TypeError。
1.3函數優先
函數聲明和變量聲明假設都在同一個作用域中,那麼是函數優先還是變量聲明優先呢??
我們看一個例子
foo(); //1 證明是函數聲明優先
var foo;
function foo(){
console.log(1);
}
foo=function () {
console.log(2);
}
在以上代碼中,我們可以看到,輸出結果爲1,從代碼執行上來看,如果是變量聲明優先的話,那麼foo()會輸出2,如果是函數優先那麼會輸出1。
這個代碼可以化成這樣的形式:
function foo(){
console.log(1);
};
foo();
foo=function(){
console.log(2);
};
注意,var foo 儘管出現在 function foo(...)聲明之前,但是它是重複的聲明(因此被忽略了),因爲函數聲明會被提升到普通變量之前。---------------------這告訴我們,在一個作用域中重複定義是糟糕的,它可能會出現各種各樣的問題!!!!
總結:
作用域中的聲明將會提升到頂部的過程------我們稱之爲提升。
提升分爲兩種,一種是變量提升,一種是函數提升,當兩者同在一個作用域中時,函數聲明先提升,隨後纔是變量提升-------兩者都是提升到當前作用域的頂部。
提升的過程中只會提升聲明,而不會提升操作語句,例 "var a=2" 將會被分成兩部分:" var a" " a=2 ",當發生提升時只有 " var a"被提升到當前作用域的頂部。 "a=2"將在原地等待執行。
避免在同一個作用域中重複定義,特別是當普通的var聲明和函數聲明在一起時!!!