[JS奇怪的世界]No.4 執行環境:創造與提升

前言

這章節將會講解到 JavaScript 中一個非常詭異的東西,那就是「創造與提升」而這部分也是許多人常搞不清楚的地方。

創造與提升

首先我們先挪用上一章節的範例程式碼 ↓

1
2
3
4
5
6
var a = 'Hello World!';

function say(){
var b = 'Tom';
console.log(b);
}

可以看到我建立了一個全域變數全域函數,而函數裡面寫了 console.log(b),這可以幫助我們在控制台 (console) 顯示資料的一個語法。

接下來我們要試著執行這些程式 ↓

1
2
3
4
5
6
7
8
9
var a = 'Hello World!';

function say(){
var b = 'Tom';
console.log(b);
}

console.log(a);
say();

預期結果應該會看到 Hello World!Tom

結果

果然與我們所預期的一樣。

那接下來如果我將將 console.log(a);say(); 放在最前面會發生什麼事情呢?

1
2
3
4
5
6
7
8
9
console.log(a);
say();

var a = 'Hello World!';

function say(){
var b = 'Tom';
console.log(b);
}

見證奇蹟的時候到了

一般來講其他程式碼會直接錯誤 (Error),因為執行順序有問題,但是在 JavaScript 並不會跳出錯誤,而是回傳給你一個值 undefined,我們先不談 Undefined 是什麼。

那在 JavaScript 真正的執行狀況是怎樣?讓我們拆開來講,首先 JavaScript 會進入兩階段 ↓

  • 創造模式
  • 賦值模式

當 JavaScript 進入創造模式後,它會先將變數及函數找出來,然後提升創造,就像這樣子 ↓

1
2
3
4
5
6
var a

function say(){
...
}
...

然而在創造變數 a 之後由於這個時候的 a 並沒有值,所以 JavaScript 在「預設執行環境」時,就會給 a 賦予一個特別的關鍵字,而這個特別的關鍵字,就是 undefined,那這個狀況又稱為 Hoisting (提升)。

所以在程式碼完整來看就是這樣子

1
2
3
4
5
6
7
var a = 'undefined'

function say(){
...
}
console.log(a);
say();

那 ‘Hello World!’ 什麼時候才會賦值呢?讓我們從範例程式碼來 ↓

1
2
3
4
5
6
7
8
9
var a = 'undefined'

function say(){
...
}
console.log(a);
say();

a = 'Hello World!';

也就是因為這個原因才導致 a 出現 undefined 的原因,但是要注意提升 (hoisting) 這兩個字常常被誤會,並不是代表真的將程式碼被移動到最前面,程式碼是並沒有任何改變的,這只是 JavaScript 的一種特別的狀況。

那總和一下前面即到現在的章節觀念,一般來講 JavaScript 有分兩階段 ↓

  • 創造階段 (creation,也就是建立執行環境)
  • Global Object(window)
  • this
  • 外部環境
  • 提升階段 (hoisting)
  • 變數
  • 函數

這邊的重點一樣在於「並不是代表真的將你的程式碼移動到最前面」,只是「編譯器」在執行時會將變數函數預先建立出來,但是要注意一件事情已經執行的函數會「預先占用記憶體」,這也是為什麼人家講過多得執行函數會導致效能較差

而變數會因為 Hoisting 特性而出現這種特別狀況,但是因為 a 還沒有被賦予,直到它被執行它才會知道它現在應該是什麼值,而這時候 JavaScript 會預設給與它一個值,也就是 undefined,直到編譯器執行 a = 'Hello World!',這時候的 a 才會變成 Hello World,讓我們來試試看吧 ↓

1
2
3
4
5
console.log(a);

var a = 'Hello World!';

console.log(a);

那結果呢?

變數 a

我們可以發現第二次執行的console a,出現 Hello World,但第一次的console 卻是 undefined,我們試著拆開來分析就會變成這樣子。

1
2
3
4
5
6
7
var a

console.log(a);

a = 'Hello World!';

console.log(a);

所以簡單來講所有在 JavaScript 中的變數一開始都是被設定成 undefined,而函數則是設定好並存入記憶體中,這章節我們理解到程式碼並不會立刻轉換成電腦可以懂得指令,而是透過「編譯器」進入幾個階段來處理(創造與提升)階段再轉換成電腦可以懂得指令。

補充資料

關於 hoisting 相關資料也可以看六角學院的共筆文件

當然也可以參考我之前所撰寫的Javascript中的hoisting

圖源

JavaScript 全攻略:克服 JS 奇怪的部分