文章目錄
- 前言
- 🧠 一、JavaScript 編譯與執行過程
- 1. 編譯階段(發生在代碼執行前)
- ? 1.1 詞法分析(Lexical Analysis)
- ? 1.2 語法分析(Parsing)
- ? 1.3 語義分析與生成執行上下文
- 🧰 二、執行階段
- ? 2.1 創建執行上下文(Execution Context)
- ? 2.2 變量環境 vs 詞法環境
- 🔁 三、作用域、作用域鏈與閉包
- ? 3.1 作用域(Scope)
- ? 3.2 作用域鏈(Scope Chain)
- ? 3.3 閉包(Closure)
- 🔄 四、循環作用域與經典陷阱
- ? 4.1 for 循環中 var 的作用域陷阱
- 🔍 五、執行上下文棧(ECS)
- 🔐 六、總結:如何理解閉包與作用域鏈的結合
- 📌 圖示記憶(簡化示意)
- 總結
前言
系統深入地講解 JavaScript 的編譯與執行原理,并結合作用域、閉包、作用域鏈等核心概念,構建一個完整的知識體系。
🧠 一、JavaScript 編譯與執行過程
雖然 JavaScript 是解釋型語言,但它在執行前仍然會經歷編譯階段(由現代 JavaScript 引擎完成,如 V8)。整體過程如下:
1. 編譯階段(發生在代碼執行前)
包括以下幾步:
? 1.1 詞法分析(Lexical Analysis)
- 將源代碼拆解成詞法單元(Token)。
- 例如:
let a = 10;
→let
、a
、=
、10
、;
。 - 負責這一階段的是詞法分析器(Lexer)。
// 代碼
let x = 10 + y;// 生成的tokens
[{ type: 'Keyword', value: 'let' },{ type: 'Identifier', value: 'x' },{ type: 'Punctuator', value: '=' },{ type: 'Numeric', value: '10' },{ type: 'Punctuator', value: '+' },{ type: 'Identifier', value: 'y' },{ type: 'Punctuator', value: ';' }
]
關聯知識:
詞法環境是 JavaScript 引擎內部用來管理變量和函數作用域的機制,它是理解作用域和閉包的核心概念。
詞法環境的組成
一個詞法環境包含兩部分:
??環境記錄(Environment Record)??:存儲變量和函數聲明的實際位置
??對外部詞法環境的引用(Outer Lexical Environment)??:形成作用域鏈
// 示例代碼
let x = 10;function foo() {let y = 20;console.log(x + y);
}// 對應的詞法環境結構
globalLexicalEnvironment = {environmentRecord: { x: 10, foo: <function> },outer: null
}fooLexicalEnvironment = {environmentRecord: { y: 20 },outer: globalLexicalEnvironment
}
詞法環境的特性
??靜態性??:在代碼編寫階段就已確定(詞法作用域)
??嵌套性??:可以形成多層嵌套結構
??持久性??:被閉包引用的詞法環境不會被銷毀
? 1.2 語法分析(Parsing)
- 將 Token 轉換為 抽象語法樹(AST)。
- AST 是代碼結構的樹狀表示,每個節點代表代碼結構的一個成分。
- 語法分析器(Parser)處理這一步。
let x = 10 + y;
// 生成的AST結構
{type: "VariableDeclaration",kind: "let",declarations: [{type: "VariableDeclarator",id: { type: "Identifier", name: "x" },init: {type: "BinaryExpression",operator: "+",left: { type: "Literal", value: 10 },right: { type: "Identifier", name: "y" }}}]
}
? 1.3 語義分析與生成執行上下文
-
變量提升、作用域環境創建、函數聲明處理。
-
此時創建:
- 執行上下文棧(Execution Context Stack)
- 詞法環境(Lexical Environment)
- 變量環境(Variable Environment)
🧰 二、執行階段
編譯完成后,進入代碼執行:
? 2.1 創建執行上下文(Execution Context)
每個函數/全局代碼執行時,會創建一個上下文環境:
-
包含:
- 變量環境(變量/函數聲明)
- 詞法環境(作用域)
- this 綁定
- 外部環境引用(outer)
? 2.2 變量環境 vs 詞法環境
-
變量環境:
- 存儲變量、函數聲明(var/函數聲明)
-
詞法環境(Lexical Environment):
- 變量環境 + 外部環境引用(outer)
- 用于作用域鏈的構建。
🔁 三、作用域、作用域鏈與閉包
? 3.1 作用域(Scope)
-
定義變量和函數的可訪問范圍。
-
分為:
- 全局作用域
- 函數作用域
- 塊級作用域(let/const)
? 3.2 作用域鏈(Scope Chain)
- 當前執行上下文的詞法環境中包含對上級詞法環境的引用。
- 在查找變量時,從當前作用域出發,逐層向上查找,直到全局作用域。
function outer() {let a = 10;function inner() {console.log(a); // 通過作用域鏈訪問 outer 的 a}inner();
}
outer();
? 3.3 閉包(Closure)
- 閉包是函數+定義它的詞法環境的組合。
- 當一個函數“脫離”了它定義時的作用域,仍然“記住”當時的變量。
function makeCounter() {let count = 0;return function () {return ++count;}
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
count
保存在makeCounter
的詞法環境中,被返回的函數形成閉包訪問它。
🔄 四、循環作用域與經典陷阱
? 4.1 for 循環中 var 的作用域陷阱
for (var i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 輸出三個 3}, 0);
}
- 原因:
var
沒有塊級作用域,i 綁定在同一個詞法環境中。 - 修復方法:使用
let
(創建新的詞法環境)
for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i); // 輸出 0 1 2}, 0);
}
🔍 五、執行上下文棧(ECS)
每當函數調用時,會創建新的執行上下文并壓棧:
- 創建全局執行上下文 → 入棧
- 調用函數 → 創建函數執行上下文 → 入棧
- 函數執行完畢 → 執行上下文出棧
🔐 六、總結:如何理解閉包與作用域鏈的結合
- JavaScript 中函數定義時就“捕獲”了其父級詞法環境(靜態作用域)。
- 執行時,通過作用域鏈查找變量,先本地再向上查找。
- 如果內部函數延遲執行(異步或返回),那么閉包可以“保持”對外部變量的訪問。
📌 圖示記憶(簡化示意)
makeCounter() 創建執行上下文
└── 詞法環境 {count = 0,outer = Global}返回的匿名函數 閉包:
└── 詞法環境 {outer = makeCounter.LE}
總結
- ??詞法環境??是 JavaScript 作用域管理的核心機制,具有靜態性和嵌套性
- 循環中使用 let 會為每次迭代創建新的詞法環境副本
- ??詞法分析??和??語法分析??是編譯的前期階段,與運行時詞法環境不同
- JavaScript 引擎通過這種機制實現塊級作用域和閉包功能
- 理解這些概念有助于編寫正確的作用域代碼和調試復雜的作用域問題