JavaScript 核心 - 來講講提升(hoisting)


Posted by ai86109 on 2020-09-29

前言

先看看這段程式碼

console.log(a)
var a = 10

照 JavaScript 逐行執行的規則,應該會印出 a is not defined,但這邊卻印出 undefined。

這裡的變數宣告被提升到最上面,這個現象就是 hoisting

如果你只想了解最基本的提升,就差不多是這樣了。


你真的懂 hoisting 嗎?

其實深入去研究 hoisting,才知道有很多概念沒有搞懂。

首先,hoisting 是跟變數有關的,所以他只會發生在他的 scope 裡面

var a = 'global'

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

test()

可能會很多人以為印出的是 global

但剛說到 hoisting 只會發生在他的 scope 內,所以你可以把上面那段看成以下:

var a = 'global'

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

test()

印出的就會是 undefined

-

那再來看一個

function test(){
  console.log(a)
  var a = 'local'
  function a(){

  }
}

test()

這邊會印出 function: a

你可能會覺得是順序的問題,但把兩個宣告互換,一樣會得到 function: a,代表 function 會佔有優先權。

那如果有兩個 function 呢?

後面宣告的會蓋掉前面的,很合理。

這邊來說明一下優先順序:

  1. function
  2. argument (傳入 function 的參數)
  3. var

但要注意的是,如果 a 重新宣告

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

test(123)

答案就會變成 456,因為 a 又重新宣告,並且有值。

所以說沒有值就不會影響嗎? 對!

function test(){
  var a
  console.log(a)
}

test(123)

會印出 123,而忽略掉沒賦值的 a


Hoisting 運作的規則

那 hoisting 實際上是怎麼運作的呢?

根據 ECMAScript 的規範,每當你進入一個 function 的時候,就會產生一個 EC(Execution Contexts),並且把 EC 放到 stack 中,一層一層疊上去,EC 裡面包含這個 function 的所有資訊。

最上層的是正在運行的 EC,執行完畢便會抽離;最底層的則是 Global Execution Contexts。

那我們剛剛說到 EC 裡面包含這個 function 的所有資訊,指的是什麼呢?

每個 EC 都有 VO(Variable Object),裡面所有宣告的變數或 function 都會變成他的 properties。

以這個程式碼為例:

function test(){
  var a = 1
}

他的 VO 就可以看成像這樣的物件

VO: {
  a: 1
}

-
當我們 call 這個 function 並帶參數時,他便會幫參數初始化

function test(a, b){

}

test(123)

他的VO就會長成以下這樣:

VO: {
  a: 123
  b: undefined
}

有傳的參數就會寫入並且賦值,沒傳的就會初始化成 undefined

-

而當我們進入 EC 時,初始化後如果碰到同名的 function 時,這個值就會被取代成這個 function。

function test(a){
  function a(){

  }
}

test(123)

他的 VO 就會長成這樣

VO: {
  a: pointer to function a
}

進入 EC,處理完 function 的 case 後,便會開始初始化宣告的變數。

如果這個宣告的變數,在 VO 已經存在且有值,則忽略這個宣告。

如果發現這個宣告的變數 VO 裡面沒有,則初始化這個宣告成 undefined,接下來才開始逐行執行。


let & const 的 hoisting

有發現講了這麼久的 hoisting,卻一直沒提到 let 和 const 嗎?

那是因為他們的機制有些許不同

先看一段程式碼

function test(){
  console.log(a)
  let a = 10
}

test()

按照之前的邏輯你應該會認為要印出 undefined

但實際上會印出 a is not defined

這是為什麼呢?

難道 let & const 沒有 hoisting 嗎?

其實是有的!一樣可以看成以下

function test(){
  let a
  console.log(a)
  a = 10
}

test()

但是不一樣的是,從進入這個 EC 到 a 被賦值之前,你不能夠去存取 a,否則就會出現錯誤。

而進入這個 EC 到 a 被賦值之前的這個區間,被稱為 TDZ(Temporal Dead Zone)

以上的內容是參考 Huli 大大的文章所寫下的筆記,更詳細、深入的可以參考這篇:我知道你懂 hoisting,可是你了解到多深?


#hoisting #javascript







Related Posts

[Java_note]多載(Overload), 覆寫(Override), 多型(Polymorphism)

[Java_note]多載(Overload), 覆寫(Override), 多型(Polymorphism)

Day 22-Pong Game

Day 22-Pong Game

讓WebStorm識別alias

讓WebStorm識別alias


Comments