# Hoisting 與 JavaScript 運作原理(1)
> JS 是直譯式 interpreted / 編譯式 compiled 語言?

儘管一般而言 JavaScript 被歸類為直譯式語言,但實際上是一種編譯式語言,但它並非事先就被編譯好,其實主流 JS 引擎都有編譯這個步驟。
簡單來說,JS 程式碼通常是在『剛好要被執行之前』編譯完成,然後即刻執行它。
****
### 背景知識:專有名詞
#### Syntax parser 語法解析器
>逐字讀取我們所撰寫的程式碼,解析語法是否正確,轉譯成電腦指令.

<br>
#### Execution context 執行環境
>一個 wrapper ,協助管理正在執行的程式
- 又可稱為**執行背景空間**、**執行情境**
- 決定現在要執行的程式是誰
- 當任何 JS code 被執行時候,它都是在某一個 Execution Context 被執行的
Javascript 共會建立兩種執行環境
- 全域執行環境 (Global Execution Context)
- 函式執行環境 (Function Execution Context)
<br>
#### Lexical environment 詞彙環境
> 程式碼在程式中實際的位置.
對javascript,程式碼寫的位置不同代表不同意思,所謂的詞彙環境就顯得很重要
<br>
****
### Global Environment 全域環境 / Global Execution Context 全域執行環境
> JS Engine 在開始執行任何程式碼之前,會先創造一個全域執行環境,裡頭有 **global object** 以及變數 `this`
*即使沒有程式碼都會被創造*

#### Global Object
- 對於 browser 而言,global object 是 window object
- 每個視窗有各自的global execution context 和 global object
#### `this`
一個特別的變數
<br>
```+
var a = "Hello World";
function b(){
}
```


#### *Global 全域*
>不在 function 裡*
在 JS 中,當我們宣告的 functions 和用 `var` 宣告的 variables 不在 function 之中,這些 variable 和 functions 就會 attached to global object
(用 `let` 和 `const` 宣告的變數會被儲存在其他地方)
<br>
****
## Execution Context , Creation Phase & Execute Phase

- 當我們的 JS code 被執行時,執行環境 Execution Context 首先被創造
- 當這段 code 不在 function 中,我們的Execution Context 會包含一個 JS Engine 創造的 Global Object 以及指向這個 global object 的 `this`
- 另外,Execution Context 之中也會創建**外部環境(Outer Environment)**
*對於 Global 環境來說,它的外部環境是 `null`*
- 在上述之後,才會進入Execution phase ,開始一行一行執行程式碼
**JS Engine 在執行我們的 code 時,幫我們創造了 Execution Context 與其他這些我們並沒有寫在 code 裡東西**
<br>
## 創建階段 (Creation Phase) 與 Hoisting
#### Hoising 錯誤觀念
~~這個名詞事實上會造成一些誤解,以為所有的變數與函式都會先被『提升』到整份 code 的最上方~~
```+
b();
console.log(a);
var a = "Hello World";
function b(){
console.log('called b!');
}
```
要考慮 JS Engine 如何運作與執行我們的 code
>**Hoisting 這個現象 ,或是說我們可以在宣告之前,某個程度 access 一個變數或函式,是由於JS Engine 在 compile 我們的code時,在 Execution Context 裡分成 Creation Phase 和 Execute Phase 這兩個階段**
<br>
### Creation Phase 創建階段

- 建立**全域物件** 和 **`this`**
全域物件只有在全域執行環境的時候才會參照到,`this` 則是一定會建立。
- 建立**外部環境 (Outer Environment)**
對於全域執行環境而言,就沒有 Outer Environment,對於函式執行環境而言,如果 function b 包在 function a 裡面,那 function b 的外部環境就是 function a
- **空出給變數和函式的記憶體空間**
sets up the memory space for the variables and functions
*這個動作造成 hoisting 的現象*
<br>
### Hoisting :空出給變數和函式的記憶體空間
> **在 code 進入 Execution Phase 執行階段之前,JS Engine 已經為這個 Excution Context 的所有 variables 與 functions 設置好記憶體空間。**
也就是說,variables 與 functions 在 code 被一行一行執行之前,已經存在於記憶體之中
<br>
#### 創建階段時的函式 Function in creation phase
當我們用 function statement (declaration) 定義一個函式時,整個 **`{}`** 內的 code 與函式名稱,都被放入記憶體之中
<br>
#### 創建階段時的變數 Variable in Creation Phase
- 在 Execution Phase 當程式碼被一行一行執行時,變數才會被賦值
- 因為當 JS Engine 為變數設置記憶體空間時,它還不知道這個變數在執行過程中的 value 會是什麼
- 所以在 Creation Phase 時,JS Engine 會將所有變數放入記憶體,**如果是用 `var` 宣告變數,變數暫時的以 `undefined ` 作為 placeholder** ( meaning: I don't know what this value is yet.)
**所有用 JS variable 在初始的值都是設置成`undefined `**
(不過只有`var` 會在 creation phase 被賦值 `undefined`)
再看一次上面的範例
```+
b();
console.log(a);
var a = "Hello World";
function b(){
console.log('called b!');
}
```
<br>
#### 補充:關於 `undefined`
```+
var a;
console.log(a);
```
- 在 JS 中,`undefined` 是一個特殊的值/keyword,意思是:它的值還沒有被設定
比較下方:
```+
console.log(a);
```

範例:
```+
if(a === undefined){
console.log('a is undefined');
}
else{
console.log('a is DEFINED');
}
var a = 'hello';
```
**!! 我們永遠不應該自行把任何變數的值設為 `undefined`**
<br>
****
### Execution Phase 執行階段
> 實際逐行的執行程式碼

<br>
#### 其他有關 hoisting 與 `undefined`
```javascript=
var v = 5
var v
console.log(v) // 5
```
```javascript=
function test(v){
var v
console.log(v)
v = 3
}
test(10) // 10
```
```javascript=
function test(a, b, c) {
console.log(a,b,c);
}
test(10) // 10 undefined undefined
```
```javascript=
helloWorld();
var helloWorld = function(){
console.log('Hello World!');
}
// Uncaught TypeError: helloWorld is not a function
```
****
### `let` & `const` 沒有 Hoisting ?
> `let` 與 `const` 確實有 hoisting,與 `var` 的差別在於提升之後,`var` 宣告的變數會被初始化為 `undefined`,而 **`let` 與 `const` 的宣告不會被初始化為 `undefined`,而是保持uninitialized**,所以如果你在「賦值之前」就存取它,就會拋出錯誤。
依照程式逐行執行,直到被賦值的那一行以後,才會初始化這個變數。
證明 `let` 其實有被 hoisted
```javascript=
var a = 10;
function test(){
console.log(a);
let a;
}
test();
// Uncaught ReferenceError: Cannot access 'a' before initialization
```
如果在 function test 裡,沒有先將 `let a;` 的變數 `a` 存到記憶體中,那麼結果應該會是 10
#### Temporal Dead Zone
在「提升之後」以及「賦值之前」這段「期間」,變數無法被 access,我們稱此期間變數處在 Temporal Dead Zone (TDZ)。
```javascript=
function a(){
// a 的 TDZ 從這裡開始...
console.log(a); // TDZ:a只有被宣告而未被初始化,所以得到錯誤
let a; // a 的 TDZ 結束
a = 10;
}
a();
// Uncaught ReferenceError: Cannot access 'a' before initialization
```
```javascript=
function a(){
// a 的 TDZ 從這裡開始...
let a; // 這裡 a 初始化成為 undefined
console.log(a);
a = 10;
}
a();
// undefined
```
然而,
在全域環境中, `let` TDZ 顯示的錯誤與在 function 中不同
```javascript=
console.log(a);
let a = 10;
//Uncaught ReferenceError: c is not defined
```
<br>
比較:
```javascript=
function a (){
let result = subtract(1,2);
let subtract = function(a, b){
return a - b;
};
console.log(result);
};
a();
//Uncaught ReferenceError: Cannot access 'subtract' before initialization
```
```javascript=
let result = subtract(1,2);
let subtract = function(a, b){
return a - b;
};
console.log(result);
// Uncaught ReferenceError: subtract is not defined
```
```javascript=
var result = subtract(1,2);
var subtract = function(a, b){
return a - b;
};
console.log(result);
// Uncaught TypeError: subtract2 is not a function
```