JavaScript Hoisting

在 JavaScript 中 Hoisting 可以幹嘛?

黃彥鈞 Jim Huang 2023/09/04 10:16:39
677

Hoisting is not a term normatively defined in the ECMAScript specification. -- MDN

 

甚麼是 hoisting?

MDN 裡面提到 hoisting 並不是被定義的專有名詞,而是用來理解 JavaScript 在執行時如何運行的思路。

我們知道 JavaScript 是單執行緒,也就是程式碼是一行一行讀取,但是用 var 宣告變數跟宣告函式會有被提早讀取,被提升(Hoisting)的感覺。

來看看一些例子:

console.log(a); 
//ReferenceError: a is not defined
console.log(a);

var a = 10;
// undefined

可以看到當使用 var 作宣告,提早讀取時會回傳 undefined,你可以把他當作這樣:

var a;

console.log(a); //undefined

a = 10;

也就是說 var 宣告會被提升,而賦值不會被提升。

再來看看 function

test(); //'hello world'

function test(){
  console.log('hello world');
}

可以看到 function 被提早讀取了,連函式內容都被提升了。

 

Hoisting 的順序

知道了 varfunction 有 Hoisting 的效果,來看一下複雜的題目。

function test(a) {
  
  console.log(a); // 印出 a
 
  var a = 10; // var 宣告
  
  function a(){
    return 'hello world'; 
  } // function 宣告

}

test(100);

這裡面有總共有三個一樣的名字,參數、functionvar 宣告,都是 a ,誰的優先權最大呢?


直接公布答案:

//output: [Function: a]

是不是有點出乎意料,答案是 function a 本身,所以說 function 的提升會優先於 var 的宣告以及參數。

那我們繼續比下去,把 function 拿掉:

function test(a) {

    console.log(a); // 印出 a

    var a = 10; // var 宣告

}

test(100);

 

這邊就直接公布答案,答案就是 100 ,所以總結來說,優先順序是:

 

function > parameter (參數) > var 宣告

 

那 let 跟 const 咧?

在 ES6 新增了 letconst 兩個新的宣告變數方式,直接來看範例:

console.log(a);

let a = 10;
//ReferenceError: Cannot access 'a' before initialization

 

可以看到 letconst 在宣告前讀取會報錯,那是不是代表他們沒有 hoisting?

那來看一個例子:

var a = 10; // var 宣告

function test() {

    console.log(a); // 印出 a

    let a = 100; // let 宣告

}

test(); //ReferenceError: Cannot access 'a' before initialization

 

如果 let 沒有 hoisting ,應該會印出外層宣告的 a = 10,但是!卻跟上一個一樣報錯了。

所以由此可知,letconst 也有 hoisting 只是 hoisting 後會報錯,跟 var hoisting 後會被初始化為 undefined 不同而已。

 

Temporal Dead Zone

letconst 會報錯則有一個概念叫做 TDZ(暫時性死區),也就是說在 let const 被 hoisting 之後到宣告之前,都是 TDZ ,只要在 TDZ 期間試著存取變數值,就會報錯。

來看剛剛的例子:

var a = 10;

function test() {

    // let 宣告 a 的 TDZ 開始

    console.log(a); //試圖存取,報錯

    let a = 100; // a 的 TDZ 結束

}

test();

 

為甚麼可以 hoisting?

當瞭解了 hoisting 之後,又跑出一個問題,JavaScript 不是單執行緒嗎?為甚麼可以 hoisting?

這其實跟 JS 內部引擎有關係,這邊就不細講了,有興趣的可以去看我知道你懂 hoisting,可是你了解到多深?,這邊就講結論:

JS 內部引擎會在執行前編譯程式碼,也就是在這個階段 hoisting 的。

 

所以 hoisting 到底可以幹嘛?

回到標題,hoisting 到底可以做甚麼,現在宣告變數也大多用 letconst ,感覺沒什麼太大的用處。

其實在實作上明顯的用處就是:

1. 函式被提升,可以不用管函式呼叫的先後順序。

2. 函式之間可以互相呼叫,可以達成遞迴的效果。

 

結論

總結以上,列出幾個要點:

1. varfunction 會有 hoisting 的效果,var 只會提升宣告,賦值不會。

2. 提升順序:函式宣告 > 函式參數 > 變數宣告。

3. letconst 有 hoisting 但在 TDZ 間存取會報錯。

 

參考資料

MDN

我知道你懂 hoisting,可是你了解到多深?

黃彥鈞 Jim Huang