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