[JavaScriptWeird]No.59 hoisting 的原理為何

前言

在受到前面兩小節的洗禮後,對於 hoisting 應該有更明確的認知,這節影片要帶領我們從 ECMAScript 了解 hoisting 的原理為何。

hoisting & scope 小測驗

首先,讓我們打鐵趁熱,來份 hoisting 的考題吧,說不定面試會考哦?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 1;  
function test(){
console.log('1.', a);
var a = 7;
console.log('2.', a);
a++;
var a;
inner();
console.log('4.', a);
function inner(){
console.log('3.', a);
a = 30;
b = 200;
}
}
test();
console.log('5.', a);
a = 70;
console.log('6.', a);
console.log('7.', b);

請依序寫出印出來的答案是多少。

還沒看解答之前我的答案是這樣,我把內容調換過用來幫助自己思考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a = 1;  
function test(){
function inner(){
console.log('3.', a); // 8
a = 30;
b = 200;
}
var a;
console.log('1.', a); // undefined
a = 7;
console.log('2.', a); // 7
a=a+1;
var a;
inner();
console.log('4.', a); // 8

}
test();
console.log('5.', a); // 1
a = 70;
console.log('6.', a); // 70
console.log('7.', b); // 200

後來對照答案後,發現第四題粗心寫錯了,應該是印出 30 才對。

但是其他都如同我想的,這代表先前的學習、寫文章記錄加深印象是有效的,往後我也會繼續這麼做。

我判斷答案時用到的觀念有:

  • ECMAScript基本觀念
  • hoisting 順序
  • 變數作用域與範圍鏈
  • var 的特性

    可…可惡,JavaScript 的陷阱真多!

ECMAScript 如何解釋 hoisting

ECMAScript 是制定 JavaScript 的標準,因此也可以說是 JavaScript 的聖經,本節影片的作者要帶領我們如何透過 ECMAScript 的文件了解 JavaScript 的行為。

隨著時間過去 ECMAScript 的文件也會越來越多,但並不會影響原作者想要傳達的目的,本節會使用較舊的版本介紹。

本節使用文件

執行環境 (Execution Contexts)

以下節錄自文件
When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context

不過我拿去 google 翻譯看完之後發現根本不懂這敘述再寫什麼 (汗

但就之前的筆記,我對於執行環境的認知是:

  • 當 JavaScript 開始運行時,會先建立一個全域執行環境
  • 之後每進入一個函式 function 就會再度創造一個執行環境
  • 而這些執行環境會按照執行順序被堆疊起來,稱為執行堆
  • 執行堆最高的會優先被執行,執行完畢後將被移出執行堆
  • 當全域執行環境也被移出時,代表 JavaScript 程式已經運行完畢

以圖片來總結上面那些話,大概就長這樣

資料來源: [我知道你懂 hoisting,可是你了解到多深?](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/)
資料來源: 我知道你懂 hoisting,可是你了解到多深?

變數初始化 (Variable Instantiation)

以下節錄自文件
Every execution context has associated with it a variable object.Variables and functions declared in the source text are added as properties of the variable object.On entering an execution context, the properties are bound to the variable object in the following order:

文件下面還有很多落落長的敘述,有興趣可以點進去看。

我理解的大意是

  • 每一個執行環境都有一個變數物件 (variable object)
  • 在執行環境內宣告的函式或變數都會被加到變數物件內成為屬性 (properties)
  • 當進入執行環境時,屬性會按照順序綁定到變數物件上
  • 當進入執行環境時,會把函式的參數放到變數物件上,值就是當時呼叫這個函式所帶入的值,若未傳入值則初始化為 undefined
  • 對於函式,如果變數物件已經有同名的屬性,則取代裡面的值
  • 對於變數,如果宣告的變數已經重複,則什麼事情都不會發生,若未重複則將該變數初始化為 undefined

白話的用一些程式表示

1
2
3
4
5
6
7
8
variableObject: {  
a: 1
}

function test(){
var a = 1;
}
test();

  • test 函式被執行,執行環境建立並進入,裡面宣告的 a 會被加到變數物件內成為屬性

    1
    2
    3
    4
    5
    6
    7
    variableObject: {  
    a: 123,
    b: undefined
    }

    function test(a, b){}
    test(123);
  • 當進入執行環境時,會把函式的參數放到變數物件上,如果未傳入參數的值,則初始化為 undefined

    1
    2
    3
    4
    5
    6
    7
    8
    variableObject: {  
    a: point to function a,
    }

    function test(a){
    function a(){}
    }
    test(123);
  • 對於函式,如果變數物件已經有同名的屬性,則取代裡面的值

    1
    2
    3
    4
    5
    6
    7
    8
    variableObject: {  
    a: 10
    }

    function test(a){
    var a;
    }
    test(10);
  • 對於變數,如果宣告的變數已經重複,則什麼事情都不會發生

後記

透過 ECMAScript 的文件解釋,我們可以理解原來 hoisting 背後的原理是這樣,相較於前面我們對於 hoisting 的觀察是較為表面的 (所以那個時候都說,我們可以把這一段程式碼想成 …)

但現在我們透過 ECMAScript 文件了解這些規則,接下來我們要嘗試用這一套規則來回答一開始的問題。

0%