前言
透過前面的篇章我們得知, JavaScript 於執行環境時,會幫我們建立出一個「全域物件」與「this」,那麼除此之外呢?今天就是探討更多有關 JavaScript 在創造「執行環境」時做的「創造」與「提升 (hoisting)」
來點提升的範例
1 | var a = 'hello'; |
這樣寫很明顯答案會是:
如果我們改變一下順序呢?
1 | b(); |
我們可能會很直覺地說,這樣會產生錯誤,因為程式碼是逐行執行的,而 b c函式與 a 變數還沒被宣告。
但在 JavaScript 不是這樣的
b
函式正確地被執行了,但是變數 a
的值變成了 undefined
。即使函式是之後才宣告的,但仍然正確執行;變數雖然值被改變了,但仍然可以使用,這到底是為什麼呢?
我們有很多疑問,但在此先移除變數
a
看看會發生什麼事情
1
2
3
4
5 b();
console.log(a);
function b(){
console.log('called b');
}
這次我們就很直接地獲得了一個 Error , a is not defined
這些現象在 JavaScript 稱為 「提升 ( hoisting )」
「提升」的解釋很容易被誤會,例如「提升」是 JavaScript 的變數與函式被實際的從程式碼內被『提升』到最上方了,像是這樣:
1 | b(); |
自動地變成這樣
1 | var a = 'hello'; |
無論在哪宣告都沒有差別。
這樣子似乎也沒什麼錯,但實際上不完全正確,因為 a 也沒有被設值為 hello,這麼說比較像是這樣子
1
2
3
4
5
6
7 var a;
function b(){
console.log('called b');
}
b();
console.log(a);
a = 'hello';
但這樣也不對,不然比較正確的說法是什麼呢?
「提升 ( hoisting )」比較合理的解釋是這樣的:
我們要回到最開始,到執行環境剛開始的時候。執行環境剛開始的時候被分成兩階段創造,第一階段是「創造 ( creation )」 階段,在這個階段中我們透過前面篇章知道有「全域物件」、「this」、「外部環境」再創造階段裡。
因此當「語法解析器」開始轉換程式碼時,它會知道我們已經在哪創造變數、函式,這是在創造階段就被設定變數以及函式在記憶體裡,這個步驟叫做「提升 (hoisting)」
這個步驟並不是真的把我們的程式碼移動到最上方,而是在程式碼被逐行解讀之前, JavaScript 已經為變數、函式在記憶體中建立一個空間了。
正是如此,當程式被逐行執行時,可以找到在記憶體中的變數、函式。
然而,變數的情況又有點不一樣
透過上面解釋,我們知道函式已經完整地在記憶體內了,代表函式內的程式碼已經被執行了。然而下一個執行階段
1 | var a = 'hello'; |
JavaScript 為 變數 a 騰出記憶體空間時並不知道 a
是什麼值,會先放上undefined
的特殊值,直到該行程式碼真正被執行時才知道。
假如設定變數但不給值會發生什麼事,像是這樣?
1 | var a; |
所有的 JavaScript 的變數一開始都會被設定為 undefined
既然 JavaScript 有「提升」特性,是不是可以不重視順序 ?
依賴「提升」並不是好的作法,這麼做可能會遇到問題,而且在可讀性上也會大大的降低,所以雖然技術上來講可以說得通,但並不建議這麼做,可以避免一些未知的 BUG 。
後記:
透過影片講解,我們了解「提升 (hoisting)」為何物,即使函式是最後才被宣告,我們還是可以先行呼叫,因為我們寫的程式碼並不會直接被執行,而是會透過 JavaScript 的轉換,並於第一個創造階段把變數、函式設定在記憶體內,因此我們可以有限的取用它們,直到實際出現在詞彙環境 (lexcial environment)之前。
課後補充一:卡斯伯的網誌
觀念跟文章說的是一樣的,只是函式寫法不同,都會被「提升 (hoisting)」影響。文章中的函式寫法是「函式陳述式」,所以在函式前方直接調用也可以運行。另外還有一種是函式寫法是「函式表達式」,會先把函式指定給一個變數,這時候如圖中範例調用,會產生 undefined
,因為「所有的 JavaScript 的變數一開始都會被設定為 undefined」,因為是 undefined
自然也無法運行函式的內容了。
課後補充二:胡立大大寫的 techbridge 技術報
內容相當詳盡,其中有一句很濃縮,又很多人不明白的:
let 與 const 也有 hoisting 但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤。