---
# System prepended metadata

title: Hoisting 與 JavaScript 運作原理(1)

---

# Hoisting 與 JavaScript 運作原理(1)

> JS 是直譯式 interpreted / 編譯式 compiled 語言?

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/3adce451-e401-4470-a873-294b1dc6db23/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T062548Z&X-Amz-Expires=86400&X-Amz-Signature=15e6329de86677f71b7d8299950421b8956cc7759026fd7ebd2f6d9807399d2a&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22 =500x)

儘管一般而言 JavaScript 被歸類為直譯式語言，但實際上是一種編譯式語言，但它並非事先就被編譯好，其實主流 JS 引擎都有編譯這個步驟。

簡單來說，JS 程式碼通常是在『剛好要被執行之前』編譯完成，然後即刻執行它。

****
### 背景知識：專有名詞

#### Syntax parser 語法解析器
>逐字讀取我們所撰寫的程式碼，解析語法是否正確，轉譯成電腦指令.

![](https://imgur.com/Tz1ZkD8.jpg =500x)

<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` 

*即使沒有程式碼都會被創造*

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/527899af-a5ae-465b-9156-b0027121c400/Screen_Shot_2021-04-29_at_6.46.24_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T072058Z&X-Amz-Expires=86400&X-Amz-Signature=8d665ab2bcc66d77f724ebaf91c3e91f09130302d780029d2c643ea15df61b09&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-04-29_at_6.46.24_PM.png%22 =500x)

#### Global Object
- 對於 browser 而言，global object 是 window object 
- 每個視窗有各自的global execution context 和 global object

#### `this`
一個特別的變數

<br>

```+
var a = "Hello World";

function b(){
    
}
```

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b972dd02-dcf3-48a6-953f-ed5bd8f8eff6/Screen_Shot_2021-04-29_at_6.53.43_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T072219Z&X-Amz-Expires=86400&X-Amz-Signature=2b3474b390bd087a64beeff92c5a448c88cd8560c108a9b864a59a566b7d166d&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-04-29_at_6.53.43_PM.png%22 =500x)

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/7b491468-f1c7-4b69-ad31-6f1583f6e585/Screen_Shot_2021-04-29_at_6.51.20_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T072313Z&X-Amz-Expires=86400&X-Amz-Signature=b13ad698211fcfe3c6344feaa2c29cbfa5b36b6aac4b3e1283f6a4fc221893ca&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-04-29_at_6.51.20_PM.png%22 =120x)


#### *Global 全域* 
>不在 function 裡*

在 JS 中，當我們宣告的 functions 和用 `var` 宣告的 variables 不在 function 之中，這些 variable 和 functions 就會 attached to global object
（用 `let` 和 `const` 宣告的變數會被儲存在其他地方）

<br>

****

## Execution Context , Creation Phase & Execute Phase

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/3df2b3a7-2501-4569-bf02-5554ba29652c/Screen_Shot_2021-05-06_at_3.42.35_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T075258Z&X-Amz-Expires=86400&X-Amz-Signature=397a62a2445a40104a32146b4547eee5bf7fe7a9f24802a3bf31ac4e3f8d33ff&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-05-06_at_3.42.35_PM.png%22 =500x)
- 當我們的 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 創建階段
![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/3a9c39b4-09d2-4228-8f43-70ea65001b03/Screen_Shot_2021-05-06_at_4.56.28_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T081735Z&X-Amz-Expires=86400&X-Amz-Signature=9547509c23465b3e0206b928d6231c04eec12fb18141d221e28d6ed21db60dbe&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-05-06_at_4.56.28_PM.png%22 =500x)

- 建立**全域物件** 和 **`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);
```
![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/4fec4994-b70c-4641-b4fe-1a014a1e56dc/Screen_Shot_2021-05-06_at_6.22.02_PM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T092754Z&X-Amz-Expires=86400&X-Amz-Signature=73be8f77de8be0dabe2e9d9eadb3f080cec4d6dd4704fe4ad7f7917e651075e5&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-05-06_at_6.22.02_PM.png%22 =350x)


範例：

```+

if(a === undefined){
    console.log('a is undefined');
}
else{
    console.log('a is DEFINED');
}

var a = 'hello';

```
**!! 我們永遠不應該自行把任何變數的值設為 `undefined`**


<br>

****
### Execution Phase 執行階段
> 實際逐行的執行程式碼

![](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/7688a3a5-4faf-4d75-a247-ae5afaafca44/Screen_Shot_2021-05-07_at_12.21.12_AM.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210713%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210713T093725Z&X-Amz-Expires=86400&X-Amz-Signature=6ce5387aa96db323be6e40638f66f50110541a6dfe958d066c2be98623358949&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Screen_Shot_2021-05-07_at_12.21.12_AM.png%22 =500x)

<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
```










