Execution Context 執行環境
JavaScript 所有的程式碼都要在 執行環境 Execution Context 中執行,我們要搞懂 JavaScript 的行為之前,需要先了解 Execution Context 是如何運作的。
當我們要準備運行 JavaScript 程式碼時,第一個 Execution Context 就是全域物件的 Execution Context,簡稱 Global EC。
之後每建立一個新的執行環境時,這些新的 EC 就會以 call stack 的方式進行堆疊。(如果不知道 call stack 是什麼,可參考[ 筆記 ] JavaScript 進階 - Event Loop)
Execution Context 的內容有哪些?
你可以想像成 JavaScript 的物件,裡面的內容會有 VO 跟 scopeChain 跟 this:
EC: {
Variable Object (VO): {
arguments: {
...
},
FD: <reference to function>
<variable>: <value>,
},
scopeChain: [...],
this: ...
}
☞ VO / AO: Variable Object
用來初始化變數的地方,會將被宣告的變數及 function 紀錄在這邊
只有在 globalEC 裡的稱為 VO,而 functionEC 裡會叫 AO,其差異就是創建 AO 時會初始化一欄 arguments: <ArgO>
VO / AO 會用來紀錄物件的 arguments & variable & function。
每當一個新的執行環境被建立時(程式開始執行 or 被呼叫的函式開始執行),就會先初始化這個執行環境的 VO / AO,整理好所需要用到的變數及函式
我們以一個簡單的例子作為範例:
function add(a, b) {
return a + b;
}
add(3, 2)
// add 被呼叫時,進入 add 的執行環境,建立 AO 來初始化變數
AO: {
a: 3,
b: 2
}
剛剛舉得是有參數傳進去時的初始化,那如果情況變成在 function 裡面才宣告變數,變數會先被加進 AO 裡面並且初始化為 undefined,等到程式執行到賦值的那一行,AO 底下的變數才會有數值
function func() {
console.log(a); // undefined
var a = 10;
}
func()
// func 被呼叫時,進入 func 的執行環境,a 會被加到 AO 並初始化為 undefined
AO: {
a: undefined
}
// 所以此時 console.log 印出來的值是 undefined
================以上部分完成初始化,下面為開始執行之後===================
// 等到程式執行到 a = 10 的這行時,才會更新 AO
AO {
a: 10
}
如果 function 裡面有宣告 function,也會把它加進 AO,值得注意的是,如果宣告的 function 名稱跟其他變數名稱撞名,function 就會把變數蓋掉
function func() {
var a = 10;
console.log(a);
function a() {...} // a() 與 var a 撞名
}
AO: {
a: function // 撞名的情況,function 會把變數蓋掉
}
變數初始化的順序
當進入新的執行環境時,變數跟函式會被放進 AO 進行初始化,而這個初始化順序如下:
- argument 先放進 AO,並初始化為 argument 的數值
- function 放進 AO,遇到同名的變數會覆蓋過去
- variable 放進 AO,並初始化為 undefined
function test(a, b) {
function b() {...}
var c = 30;
}
test(123);
// argument 先放進 AO,並初始化為 argument 的數值
AO: {
a: 123,
b: undefined
}
// function 放進 AO,遇到同名的變數會覆蓋過去
AO: {
a: 123,
b: function
}
// variable 放進 AO,並初始化為 undefined
AO: {
a: 123,
b: function,
c: undefined
}
================以上部分完成初始化,下面為開始執行之後===================
// 等到程式執行到 c = 30 的這行時,才會更新 AO
AO: {
a: 123,
b: function,
c: 30
}
小補充:如果在 function 裡面直接賦值一個沒有被宣告的變數,這個變數會直接被宣告在 globalEC 的 VO
function test() {
a = 10; // 直接賦值一個沒有被宣告的變數
}
console.log(a); // 10
系統會把 a 宣告在 globalEC 的 VO,所以在 function 外面 console.log 會印出 10
let & const 的 TDZ 特性
let 跟 const 宣告的變數也會加到 VO / AO 並初始化為 undefined,但是它們有一個極大的差異性,就是用 let 或者 const 選告的變數,在賦值之前都不可以去存取它,否則會報錯
function test() {
console.log(a); // a is not defined
let a = 10; // 用 let 或者 const 選告的變數,在賦值之前都不可以去存取它
}
test()
在宣告到賦值之間這段不能被存取的過程被稱作 Temporal Dead Zone。
Function expression 的初始化(問題與討論)
可以參考:https://github.com/Lidemy/mentor-program-3rd-ClayGao/pull/24