[ 筆記 ] JavaScript 進階 04 - Hoisting


Posted by krebikshaw on 2020-09-25

在理解 hoisting 之前需要先了解 Execution Context 的概念,可以先參考
[ 筆記 ] JavaScript 進階 - Execution Context

什麼是 Hoisting?

我們都知道如果要印出一個沒有被宣告的變數,會出現錯誤:... is not defined
如果要印出的是有宣告但是沒有被賦值的變數,會出現 undefined

那這段程式碼應該會輸出什麼?

console.log(a);
var a = 10;

我在 a 被宣告之前印出 a,感覺上應該要出現 a is not defined
但實際上系統卻會印出 undefined,這是因為系統把宣告 a 的動作提前了,這個特性就叫做 Hoisting,可以想像成 變數 或 function 的宣告提升到作用域的最上方。

Hoisting 原理

當了解 EC 的編譯及執行這兩個階段,就不難理解為什麼會有 Hoisting 的現象。

→ Phase 1 編譯 : 初始 EC 內容
Hoisting 的重點就是因為有 EC 編譯階段,會先把 變數 跟 function 丟進 EC 的 VO 裡面

  • 變數會初始化成 undefined
  • 而 function 則指向 內容的記憶體位置
global EC: {
    VO: {
        a: undefined,
    }
}

→ Phase 2 執行 : 根據 EC 的資源執行 function
所以在執行階段時,當我們要查詢變數 a,因為 EC 的 VO 裡面已有宣告 a 的紀錄,才會造成輸出結果為 undefined。

這樣「 看似把 變數 & function 的宣告提到( 作用域 )最上面的動作,稱為 Hoisting 」,然而我們要是了解 JS 執行的過程,就可以很清楚解釋 Hoisting 的原理。

同樣名稱的arguments & Function & var,如何判斷最終提升的結果?

☞ Function VS var
function 提升的優先度會比變數高,跟宣告順序沒關係:
(而後面宣告的 function 會蓋過前面的 function)

function test() {
    console.log(a);
    function a() {};
    var a = '1';
}

test(); // => [Function: a],是 Function 而非 undefined

☞ arguments VS var

function test(a) {
    console.log(a);
    var a = 456;
}
test(123); // => 123

test() 裡面已經有 a 變數了,所以變數 a => undifined 的提升 Hoisting 等於沒有用,但如果是直接賦值,等於是在執行階段改變了 a 的值,則當然會影響到:

function test(a) {
    var a = 456;
    console.log(a);
}
test(123); // => 456

☞ Function VS arguments

function test(a) {
    console.log(a);
    function a() {
        return 'a function';
    }
}
test(123); // => [Function: a]

小補充: 那 let & const 呢?

其實這兩種變數宣告方式也是有 Hoisting 的,只是方式不太一樣,let & const 同樣會把變數提上去,但不會設成 undefined,且在賦值之前無法存取,這範圍稱為 Temporal Dead Zone (TDZ)

Hoisting 的優先度

當發生名稱衝突時、Hoisting 的優先度( 由高至低 ):

  1. 函式宣告 function declaration
  2. 函式參數 function arguments
  3. 變數宣告 variable

Hoisting 的目的

function 提升是有其必要的,可以解決這種 A call B & B call A 的互相呼叫 function 的問題。

// 验证偶数
function isEven(n) {
    if (n === 0) {
        return true;
    }
    return isOdd(n - 1);
}

// 验证奇数
function isOdd(n) {
    if (n === 0) {
        return false;
    }
    return isEven(n - 1);
}

console.log(isEven(2)); // true

☞ 名詞小補充: LHS 賦值 & RHS 查詢值

var a = 10
console.log(a)

上面這兩行有個差異,第一行的時候我們只需要知道「a 的記憶體位置在哪裡」就好,我們不關心它的值是什麼。

而第二行則是「我們只關心它的值是什麼,把值給我就好」,所以儘管兩行裡面都有a,但你可以看出來他們所要做的事情是不一樣的。

第一行的 a 我們叫它 LHS(Left hand side)引用,第二行叫它 RHS(Right hand side)引用,這邊的 left 跟 right 指的是相對於等號的左右邊,但用這種方式理解的話其實不夠精確,因此像下面這樣記就好:

  • LHS:請幫我去查這個變數的位置在哪裡,因為我要對它賦值。
  • RHS:請幫我查詢這個變數的值是什麼,因為我要用這個值。

小測驗

以下有個小測驗,可以來檢驗對 Hoisting 的熟悉程度:

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. undefined
2. 7
3. 8
4. 30
5. 1
6. 70
7. 200

參考資料:我知道你懂 hoisting,可是你了解到多深?


#hoisting







Related Posts

進階 React Component Patterns 筆記(下)

進階 React Component Patterns 筆記(下)

煙雨丹霞

煙雨丹霞

JS30 Day 16 筆記

JS30 Day 16 筆記


Comments