[JavaScriptWeird]No.3 執行環境:創造與提升

前言

透過前面的篇章我們得知, JavaScript 於執行環境時,會幫我們建立出一個「全域物件」與「this」,那麼除此之外呢?今天就是探討更多有關 JavaScript 在創造「執行環境」時做的「創造」與「提升 (hoisting)」

來點提升的範例

1
2
3
4
5
6
var a = 'hello';  
function b(){
 console.log('called b');
}
b();
console.log(a);

這樣寫很明顯答案會是:

如果我們改變一下順序呢?

1
2
3
4
5
6
b();  
console.log(a);
var a = 'hello';
function b(){
 console.log('called 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
2
3
4
5
6
b();  
console.log(a);
var a = 'hello';
function b(){
 console.log('called b');
}

自動地變成這樣

1
2
3
4
5
6
var a = 'hello';  
function b(){
 console.log('called b');
}
b();
console.log(a);

無論在哪宣告都沒有差別。

這樣子似乎也沒什麼錯,但實際上不完全正確,因為 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」、「外部環境」再創造階段裡。

克服 JS 奇怪部分 截圖

因此當「語法解析器」開始轉換程式碼時,它會知道我們已經在哪創造變數、函式,這是在創造階段就被設定變數以及函式在記憶體裡,這個步驟叫做「提升 (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,而且在賦值之前試圖取值會發生錯誤。

0%