JS 引擎如何運作:理解 Execution Context 與 Variable Object


先前已經具備對 Hoisting 的初步理解,最後我們要再來了解 Hoisting 是怎麼運作?並進一步理解 Execution Context 與 Variable Object 與 JS 引擎進一步的關係。

ECMAScript 針對這個主題,先介紹了什麼是 Execution Contexts(簡稱 EC)。當我們進入一個 function 的時候,都會產生一個 EC,裡面存放有關 function 有關的資訊,當 function 執行完之後,就會把 EC 從執行流程中刪除。

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.

每個 Execution Context 都有一個 Variable Object(簡稱 VO),在 EC 裡面宣告的變數、函式以及函式的參數,都會被加入 VO 裡。可以把 VO 想像成一個 JS 的物件。進入 EC 之後,會按照以下順序把 property(屬性)放進 VO 裡。

  1. 函式的參數,會直接放進 VO 裡面,新增一個屬性。如果參數沒有賦值,會將他的值初始化成 undefined。
  2. 若在 EC 裡面有宣告 function,也放進 VO 裡面,其值為函式回傳的內容。特別注意,如果 VO 裡面已經有同名屬性的話,直接把值覆蓋掉。
  3. 關於變數,在 VO 裡面新增一個屬性,並把值設定為 undefined。如果 VO 裡面已經有相同屬性,值不會改變。

每個 function 你都可以想成其實執行有兩個階段,第一個階段是進入 EC,第二個階段才是真的一行行執行程式。

  • 舉個例子
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. 首先先進入 global EC
//2. 產生 global VO
//3. 第一步先找參數,但 global 不是函式
//4. 第二步是找函式的宣告
//5. 接著找變數的宣告
global VO: {
    test: function,
    a: undefined
}
//6. 接著開始執行程式碼
//7. 執行到第一行 var a = 1
//8. a 的值換成 1
global VO: {
    test: function,
    a: 1
}
//9. 當呼叫 test(),就進入新的 EC
//10. 進入 test 函式裡面
//11. 第一步,找參數,沒有找到
//12. 第二步,找函式宣吿,有找到
//13. 第三步,找變數宣告
test EC
test VO: {
    inner: function,
    a: undefined
}
global VO: {
    test: function,
    a: 1
}
// 14. 初始化完成之後,接著開始執行程式碼
// 15. console.log('1.', a), a 輸出 undefined
// 16. var a = 7
// 17. a 的值變成 7
// 18. console.log('2.', a),a 輸出 7 
test VO: {
    inner: function,
    a: 7
}
global VO: {
    test: function,
    a: 1
}
// 19. a++,a 輸出 8 
// 20. 當呼叫 inner(),就進入新的 EC
// 21. inner 裡面,沒有宣告任何的參數、函式、變數,所以為空
inner VO; {
}
test VO: {
    inner: function,
    a: 8
}
global VO: {
    test: function,
    a: 1
}
// 22. 執行 inner() 裡面的執行碼
// 23. console.log('3.', a),因為 inner VO 是空的,所以往上找會找到 test 的 VO,找到 a = 8,輸出 a = 8
// 24. 執行 a = 30,往上找 test VO,a = 30
// 25. 執行 b = 200,往上找 test VO,沒有,就在 global VO 新增一個值 b = 200 (變成全域變數)
inner VO; {
}
test VO: {
    inner: function,
    a: 30
}
global VO: {
    test: function,
    a: 1
    b: 200
}
// 26. inner() 執行結束後,刪除
// 27. console.log('4.', a),輸出 a = 30
test VO: {
    inner: function,
    a: 30
}
global VO: {
    test: function,
    a: 1
    b: 200
}
// 28. test 執行完之後,
// 29. console.log('5.', a),a = 1
global VO: {
    test: function,
    a: 1
    b: 200
}
// 30. a = 70
// 31. console.log('6.', a),a = 70
// 32. console.log('7.', b),b = 200
global VO: {
    test: function,
    a: 70
    b: 200
}
// 33. global 程式執行完之後,結束

let 和 const 的詭異行為

  • 複習一下 var,再來看 let/const
console.log(a)
var a = 10 
// 印出 undefined
console.log(a)
let a = 10
// a is not defined
  • let/const 提升之謎
let a = 10
function test() {
    console.log(a)
    let a = 10
}
test()
// a is not defined

TDZ:Temporal Dead Zone (暫時性死去)

let 與 const 確實有 hoisting,與 var 的差別在於提升之後,var 宣告的變數會被初始化為 undefined,而 let 與 const 的宣告不會被初始化為 undefined,而且如果你在「賦值之前」就存取它,就會拋出錯誤。

  • 舉個例子
let a = 10
function test() {
    console.log(a) // 在賦值之前存取 a,就會拋出錯誤
    let a = 10
}
test()
// a is not defined







Related Posts

API 留言板練習-Part1 : 打造後端 API

API 留言板練習-Part1 : 打造後端 API

30-Day LeetCoding Challenge 2020 April Week 2 || Leetcode 解題

30-Day LeetCoding Challenge 2020 April Week 2 || Leetcode 解題

Go json and embedded struct

Go json and embedded struct




Sponsored



Comments