JavaScript --有關提升

Reaper622發表於2018-10-05

先有雞還是先有蛋?

當我們執行下面一段程式碼:

a=2;
var a;
console.log(a);
複製程式碼

他會輸出什麼呢?可能有部分人會認為是undefined,因為 var a 在 a=2 之後宣告,所以會被重新賦值故為undefined。 But,告訴你一個不幸的訊息,它真正的輸出結果為2.

那麼我們再來看下一段程式碼:

console.log(a);

var a = 2;
複製程式碼

鑑於上一段程式碼的結果,我們主觀性的認為它的結果也應該是2,也有人可能會說,a在使用之前沒有宣告,會丟擲一個ReferenceError異常。但是我要很遺憾的告訴你,它的結果是 undefined

為了搞明白這個一點,我們需要深度剖析這個問題。

有關編譯器

我們首先要了解,一段JavaScript程式碼在被引擎解釋之前會先被編譯器編譯。編譯的其中一項工作就是找到所有的宣告,並且將它們與合適的作用域關聯起來。

因此我們得知:變數和函式的所有宣告都會在任何程式碼被執行前首先被編譯器處理。

當我們看到 var a = 2;時我們可能會認為這只是一個宣告。但在JavaScript眼中,它分為了"var a"和"a=2"兩部分,"var a"會在編譯階段執行,而"a=2"則在程式碼執行到這裡時再執行。

所以,第一段程式碼我們可以化為:

var a;
a = 2;
console.log(a);
複製程式碼

第二段程式碼化為:

var a;
console.log(a);
a = 2;
複製程式碼

在第二段程式碼執行console輸出時,a還沒有被賦值,故結果為undefined.

我必須再提醒你們一次,當編譯器對變數執行提升時,只有宣告本身得到了提升,而賦值或者其他邏輯執行則會停留在原地,在程式碼執行到這一行時才會執行

並且,每個作用域都會有提升操作,但它會只相對於它自身進行提升,一個作用域內的提升並不會提升到程式(全域性)的最上方。

此外,函式宣告也是會被提升的,但他的表示式卻不會被提升。

foo();  //報出TypeError!而不是ReferenceError.
var foo = function bar(){
    //some code
}
複製程式碼

這段程式碼的解釋為,foo被提升並分配給所在的作用域,所以它不會報出ReferenceError,但是foo 後半段的賦值操作並沒有得到提升,所以foo此時是undefined,對undefined進行函式呼叫時非法操作,故報出了TypeError異常。

函式是那個女士!

我們都知道一個國際性原則:女士優先!所以函式是那個女士也就表明了“函式優先”!

函式宣告和變數宣告都會被提升,但是函式會被首先提升,然後才是變數。

話不多說,讓我們看下面這段程式碼

foo();  //結果為1

var foo;

function foo(){
    console.log(1);
}

foo = function(){
    console.log(2);
}
複製程式碼

它輸出的結果為1 並不是 2!Why!因為在引擎的眼中,這段程式碼是長這個樣子的:

function foo(){
    console.log(1);
}

foo();

foo = function(){
    console.log(2);
}
複製程式碼

提醒一點,肅然var foo出現在function foo()之前,但是他是重複的宣告從而會被忽略,因為函式優先!

儘管重複的var宣告會被忽略,但是後出現的函式宣告可以覆蓋從前的函式宣告

foo(); //3

function foo(){
    console.log(1);
}
var foo = function(){
    console.log(2);
}
function foo(){
    console.log(3);
}
複製程式碼

相信各位看到這些已經眼花繚亂,所以在同一個作用域中進行重複定義是非常不被提倡的,會出現各種意想不到的Bug。如果你不想開發的大部分時間都要去debug,我建議你不要在同一個作用域中重複定義。

總結一下

  • 我們眼中的var a = 2;在引擎的眼中卻是var a 和 a = 2這兩個單獨的宣告,第一個是編譯階段執行,第二個是執行階段執行。
  • 函式宣告會優先於變數宣告,函式宣告後的變數宣告會被認為是多餘的而忽略,但後面的函式宣告可以覆蓋之前的函式宣告!
  • 不要在同一個作用域中進行重複定義!不要在同一個作用域中進行重複定義!不要在同一個作用域中進行重複定義!

相關文章