六角學院 JS 核心筆記 (四)【執行環境與作用域】- 執行環境 (Execution Context) 與 執行堆疊 (Execution Stack)

前言

函式執行時,內部變數的作用域有個執行限制範圍,這個範圍就是在執行環境內。

執行堆疊是建立在執行環境基礎上的「函式呼叫和執行的順序」。

執行環境 Execution Context

所謂的執行環境,就是當函式被呼叫執行時才會產生的一個限制環境,在函式內部宣告的變數,只有在該執行環境中 (函式中) 才能使用,執行環境外 (函式外) 就不能使用。

一般函式

重新呼叫函式時產生的的執行環境,和前一次呼叫產生的的執行環境,是不相同的東西。因此在舊的執行環境中的變數的值,不能直接在新的執行環境中使用 (除非傳值)。

1
2
3
4
5
6
function callMe(){
var name = "Jenifer";
var num = 1;
console.log("叫 " + name + " " + num + " 次");
num = num + 1;
}

上面的程式碼中,定義函式後,執行環境還不會產生。當 callMe() 被呼叫後,執行環境才會產生。而且不管呼叫多少次, num 的值都不會沿用到新的執行環境 (callMe() 函式) 中。

1
2
3
4
5
6
7
8
9
10
11
// 第一個 callMe 執行環境產生
callMe();
// 第一個 callMe 執行環境結束
//
// 第二個 callMe 執行環境產生
callMe();
// 第二個 callMe 執行環境結束

// 輸出結果:
// 叫 Jenifer 1 次
// 叫 Jenifer 1 次

如上圖中所示,每一次執行,就會產生自己的作用域還有自己的 this ( this 會在之後學到)。

特殊執行環境 - 全域

JS 程式可以用來啟動網頁或後端 Node.js,而 JS 程式一啟動,可能是 (用瀏覽器) 開啟網頁或 Node.js 後端一啟動時,就會產生一個全域的執行環境,裡面有一個全域的變數 (前端是 window,後端是 global),也有全域自己的 this,但是 this 和 window 或 global 是相同的。

執行堆疊 Execution Stack

在講「函式呼叫和執行的順序」前,先介紹一個東西叫做 Stack。

Stack

Stack 中文叫做堆疊,是一種很基本的資料結構型態。它是線性資料結構,遵循特定的執行順序,也就是先進後出後進先出

Stack is a linear data structure which follows a particular order in which the operations are performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out).

概念像是下面圖中的網球和筒子,先放進去的網球,最後才被拿出來。

因此,執行引擎在控制各個函式的呼叫和執行順序時,就是利用 Stack 的概念。

舉例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function thirdFunc(name) {
var name = "Marry";
console.log(name);
}

function secondFunc(name) {
console.log(name);
thirdFunc(name);
}

function firstFunc() {
var name = "Jenifer";
console.log(name);
secondFunc(name);
}

firstFunc();
secondFunc("Peter");

// 輸出結果:
// Jenifer
// Jenifer
// Marry
// Peter
// Marry

如上方的程式碼,JS 程式啟動後,產生 全域執行環境 並且以 stack 資料結構的形式,儲存在電腦記憶體中。當程式呼叫 firstFunc() 時,會產生 firstFunc 執行環境,堆疊在 全域執行環境 上。接下來執行第 14 行的 secondFunc(name),並產生 secondFunc 執行環境,堆疊在 firstFunc 執行環境 上。接著執行第 8 行的 thirdFunc(name),並產生 thirdFunc 執行環境,堆疊在 secondFunc 執行環境 上。

thirdFunc(name) 執行完畢後,thirdFunc 執行環境 被從 stack 中刪除,將記憶體釋放。接著 secondFunc(name) 執行完畢後,secondFunc 執行環境 被從 stack 中刪除,將記憶體釋放。接著 firstFunc() 執行完畢,firstFunc 執行環境 被從 stack 中刪除。剩下 全域執行環境 還在 stack 中,因為 JS 程式 (網頁) 還沒有關閉。

接下來執行第 18 行 secondFunc("Peter") 以此類推。

第 17、18 行完整的過程如下:

開發人員工具 Debugger 模式查看執行堆疊

Sources > 暫停按鈕 (暫停目前的 JS 程式) > 重新整理網頁,就能進入 Debugger 模式。

重新整理網頁,代表重新執行程式,可以看到在 Call Stack 中,目前是在 全域執行環境Scope 代表作用域在全域 Global。按向左箭頭 Step 表示一行一行執行程式碼並且顯示變數的值等等。

進入 firstFunc(),可以看到 firstFunc 執行環境,堆疊在 全域執行環境 上。Scope 作用域在 Local 函式內。

進入 secondFunc()secondFunc 執行環境 堆疊在 firstFunc 執行環境 上。Scope 作用域在 Local 函式內。

進入 thirdFunc()thirdFunc 執行環境 堆疊在 secondFunc 執行環境 上。Scope 作用域在 Local 函式內,一開始 name 的值是 Jenifer,後來被重新宣告的 Marry 取代。

參考資料:
六角學院:JavaScript 核心篇 - 邁向達人之路
GeeksforGeeks:Stack Data Structure