1. 程式人生 > >讀書筆記《你不知道的JavaScript上卷》1.4提升

讀書筆記《你不知道的JavaScript上卷》1.4提升

4.1 首先看現象

程式碼1:

a = 2;
var a;
console.log( a );//列印2

程式碼2:

console.log( a );//列印undefined
var a = 2;

上面這兩個就是體現了JS中變數提升的簡單示例,為什麼是這樣呢?

4.2 提升的本質

之前說過JS程式碼的執行會經過兩個階段,一個是編譯階段,一個是執行階段。現有語句“var a = 2;“其實相當於“var a;”和“a = 2;”兩條語句,其中第一個宣告語句是在編譯階段進行的,第二個賦值語句是在執行階段進行的,正因為這樣所以宣告語句的執行要先於賦值語句,所以就會有上面這種現象。

提升:由於宣告所在的編譯階段要先於賦值所在的執行階段,這個過程就好像變數和函式宣告從它們在程式碼中出現的位置被“移動”到了最上面,這個過程就叫做提升。

這就好像4.1中的程式碼2可以看成如下程式碼(提升是由於上述2個階段引起的,通常我們都會把變數的提升理解為程式碼寫在作用域的第一行,這樣有助於我們分析程式碼,並且結果跟正確的結果是一致的):

var a;//宣告相當於出現在作用域的第一行

console.log( a );//執行的時候還沒有執行到賦值語句所以列印undefined
a = 2;

這樣一來一切就明朗了。

4.3 函式的提升

函式和變數一樣也會提升,這裡會牽扯到一個順序的問題,看如下程式碼:

foo(); // 這裡列印3
function foo() {//這個foo是函式的宣告會被提升,相當於放在了作用域的起始位置
    console.log(1);
}
var foo = function() {//這個foo是變數(雖然賦值以後是函式),所以變數會被提升(但沒有賦值),不過由於上面的foo已經提升了,也就是已經宣告過了,這種情況這裡的foo單純的var宣告將會被忽略。
    console.log(2);
};
function foo() {//這個foo也會被提升,並且會覆蓋之前提升的foo,所以列印3
    console.log(3);
}

由上可知,對於同一個名稱的提升,如果變數寫在前面的話,那麼函式的提升會覆蓋變數的提升,如果函式寫在前面的話,後面變數的提升(var宣告這種情況)將會忽略,所以可以總結出函式的提升優先於變數的提升

4.4 變數提升的坑

現在考慮一種比較特殊的提升,程式碼如下:

foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
    console.log(123);
};

上述程式碼中foo是TypeError應該不會有什麼疑問的,由於foo是var宣告的所以會提升,當執行的時候foo還沒有賦值所以是undefined,所以直接以函式的形式呼叫是不行的,所以TypeError。對於bar這種情況,由於bar被賦值給foo,這種具名函式將會在函式外面被忽略,所以相對於函式外部來說bar相當於不寫,故呼叫bar,會出現ReferenceError,這裡說明一點,即使bar在最後一行呼叫也是不行的,因為bar在函式外部是忽略的。上述程式碼可以看做如下程式碼:

var foo;//foo提升了

foo(); // TypeError
bar(); // ReferenceError

foo = function() {
    var bar = foo;//只有在函式內部才能訪問到bar
    console.log(123);
};