前言
這篇文章是第十六周的功課,藉由解釋以下程式碼的運作來了解 hoisting 的機制。廢話不多說,我們開始吧!
題目
var a = 1
function fn(){
console.log(a)
var a = 5
console.log(a)
a++
var a
fn2()
console.log(a)
function fn2(){
console.log(a)
a = 20
b = 100
}
}
fn()
console.log(a)
a = 10
console.log(a)
console.log(b)
回答
這一題一樣 cosplay JS engine 跑一次!
Execution Context
engine 進入 global execution context(下稱EC)
產生了一個 varaiable object(下稱VO),裡面紀錄這個 variables 以及 function。
global:{
VO:{
a: undefined,
fn: function,
}
scopechain: {
global.VO
}
}
fn() 被呼叫執行,進入 fn() 的 EC
產生了一個 fn 的 EC 並在裡面建立 fn() 的 VO 以及 scope chain。
fn:{
VO:{
a: undefined,
fn2: function
}
scope chain:{
自己的 scope: fn.VO,
global 的 scopechain: global.scopechain = global.VO
}
}
global:{
VO:{
a: undefined,
fn: function,
}
scopechain: {
global.VO
}
}
fn2() 被呼叫,進入 fn2 的 EC
fn2:{
VO:{
}
scopechain:{
自己的 scope: fn2.VO,
fn 的 scopechain: fn.scopechain = fn.scope + gloal.scope
}
}
fn:{
VO:{
a: undefined,
fn2: function
}
scope chain:{
自己的 scope: fn.VO,
global 的 scopechain: global.scopechain = global.VO
}
}
global:{
VO:{
a: undefined,
fn: function,
}
scopechain: {
global.VO
}
}
開始執行囉!
- 定義變數 a 為 1。
- 呼叫 fn()。
- 開始跑 fn()。
- 印出 a
- fn() 裡面雖有 a,但因為是在之後才宣告,目前 只有被 hoisting 進去 VO 裡,值仍然是 undefined, 所以第一個會印出
undefined
。 - 定義變數 a 為 5,VO 裡的 a 變為 5。
- 印出 a,這次 a 的值為 5,所以印出
5
。 - 執行 a++,所以 a 變為 6。
- 宣告 a ,但 a 在前面早被宣告了,所以這邊的操作無效直接跳過。
- 呼叫 fn2()。
- 印出 a,fn2() 的 VO 裡沒有 a,所以沿著 scopechain 往上找,在 fn() 裡找到變為 6 的 a,所以印出
6
。 - a 的值改為 20,但 fn2() 裡沒有 a,所以一樣沿著 scopechain 往上找,在 fn() 裡找到變為 6 的 a,改變成 20。
- b 的值改為 100,但是沿著 scopechain 往上找,發現根本沒有任何 EC 裡有宣告過 b,所以 b 直接變成值為 100 的全域變數,換言之,在 global 裡的 VO 加上變數 b,且值為 100。
- 印出 a 值,此時的 a 值已經被改為 20,所以印出
20
。 - 印出 a 值,因為已經回到 global 的 EC 裡,所以在 fn() 裡面一大塊裡對 a 的操作已經無關,只需要檢查 a 在 global 的 VO 裡的值,得出答案為 1,所以印出
1
。 - a 的值改為 10。
- 印出 a,
10
。 - 印出 b,
100
。