JavaScript 中的執行上下文和執行棧
阿新 • • 發佈:2021-02-21
# **JavaScript - 原理系列**
在日常開發中,每當我們接手一個現有專案後,我們總喜歡先去看看別人寫的程式碼。每當我們看到別人寫出很酷的程式碼的時候,我們總會感慨!寫出這麼優美而又簡潔的程式碼的兄弟到底是怎麼養成的呢?
我要怎樣才能達到和大佬一樣的水平呢!好了,廢話不多說,讓我們切入今天的主題。
## **一、執行上下文**
簡而言之,【執行上下文】就是JavaScript 程式碼被解析和執行時所在環境的抽象概念, 在JavaScript 中執行任何的程式碼都是在它的執行上下文中執行。
在執行JavaScript程式碼時,每當需要執行程式碼時,執行程式碼會先進入一個環境(瀏覽器、Node客戶端),這時就會為該環境建立一個執行上下文,它會在你執行程式碼前做一些準備工作,如確定作用域,建立全域性、區域性變數物件等。
**執行上下文的分類**
- **全域性執行上下文:**
這是預設的、最基礎的執行上下文。不在任何函式中的程式碼都位於全域性執行上下文中。
它做了兩件事:
- - 建立一個全域性物件,在瀏覽器中這個全域性物件就是 window 物件。
- 將 `this` 指標指向這個全域性物件。一個程式中只能存在一個全域性執行上下文。
- **函式執行上下文:**
每次呼叫函式時,都會為該函式建立一個新的執行上下文。每個函式都擁有自己的執行上下文,但是隻有在函式被呼叫的時候才會被建立。一個程式中可以存在任意數量的函式執行上下文。每當一個新的執行上下文被建立,它都會按照特定的順序執行一系列步驟,具體過程將在本文後面討論。
- **Eval 函式執行上下文:**
執行在 `eval` 函式中的程式碼也獲得了自己的執行上下文,但由於 Javascript 開發人員不常用 eval 函式,所以在這裡不再討論。
### 執行上下文的數量限制(堆疊溢位)
執行上下文可存在多個,雖然沒有明確的數量限制,但如果超出棧分配的空間,會造成堆疊溢位。常見於遞迴呼叫,沒有終止條件造成死迴圈的場景。
下面是示例程式碼:
```javascript
// 遞迴呼叫自身
function foo() {
foo();
}
foo();
// 報錯:Uncaught RangeError: Maximum call stack size exceeded
```
**Tips:**
JS是“單執行緒”的,每次只執行一段程式碼
## **二、執行棧**
JS中的執行棧,也就是在其它程式語言中所說的“呼叫棧”,是一種擁有 LIFO(後進先出)資料結構的棧,被用來儲存程式碼執行時建立的所有執行上下文。
當 JavaScript 引擎第一次遇到你的指令碼時,它會建立一個全域性的執行上下文並且壓入當前執行棧。每當引擎遇到一個函式呼叫,它會為該函式建立一個新的執行上下文並壓入棧的頂部。
引擎會執行那些執行上下文位於棧頂的函式。當該函式執行結束時,執行上下文從棧中彈出,控制流程到達當前棧中的下一個上下文。
**棧資料結構**
![](https://cdn.jsdelivr.net/gh/viacooter/dragonImages@main/imgs/20210221135812.png)
現在讓我們用一段程式碼來理解執行棧
```JavaScript
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
```
下圖是上面程式碼的執行棧
![](https://cdn.jsdelivr.net/gh/viacooter/dragonImages@main/imgs/640)
當上述程式碼在瀏覽器載入時,瀏覽器的JavaScript 引擎會建立一個全域性執行上下文並把它壓入當前執行棧。當遇到函式呼叫時,JavaScript 引擎為該函式建立一個新的執行上下文並把它壓入當前執行棧的頂部。
當從 `first()`函式內部呼叫 `second()`函式時,JavaScript 引擎為 `second()` 函式建立了一個新的執行上下文並把它壓入當前執行棧的頂部。當 `second()`函式執行完畢,它的執行上下文會從當前棧彈出,並且控制流程到達下一個執行上下文,即 `first()` 函式的執行上下文。
當 `first()` 執行完畢,它的執行上下文從棧彈出,控制流程到達全域性執行上下文。一旦所有程式碼執行完畢,JavaScript 引擎從當前棧中移除全域性執行上下文。
### **The Creation Phase**
在 JavaScript 程式碼執行前,執行上下文將經歷建立階段。在建立階段會發生三件事:
1. **this** 值的決定,即我們所熟知的 **This 繫結**。
2. 建立**詞法環境**元件。
3. 建立**變數環境**元件。
所以執行上下文在概念上表示如下:
```javascript
ExecutionContext = {
ThisBi