JavaScript 的执行过程可以分为几个关键阶段,包括解析、编译、执行等。以下是详细过程:
1. 解析(Parsing)
解析是将 JavaScript 源代码转换为计算机可以理解的结构的过程。它分为两个阶段:
1.1. 词法分析(Lexical Analysis)
作用:将源代码字符串分解为有意义的词法单元(tokens)。
let x = 10 + 20;
如上代码会被分解为:
1. let(关键字)
2. x(标识符)
3. =(运算符)
4. 10(数字)
5. +(运算符)
6. 20(数字)
7. ;(分号)
1.2. 语法分析(Syntax Analysis)
作用:根据 JavaScript 的语法规则,将词法单元转换为抽象语法树(AST)。
let x = 10 + 20;
如上代码,对应的 AST 可能如下:
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": { "type": "Identifier", "name": "x" },
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": { "type": "Literal", "value": 10 },
"right": { "type": "Literal", "value": 20 }
}
}
],
"kind": "let"
}
2. 编译(Compilation)
JavaScript 引擎(如 V8)会将 AST 转换为可执行的代码。现代 JavaScript 引擎通常使用即时编译(JIT)技术:
2.1. 即时编译(Just-In-Time Compilation, JIT)
1. 解释器:将 AST 快速转换为字节码并执行;
2. 编译器:将热点代码(频繁执行的代码)优化为机器码,以提高执行效率;
3. 优化策略:
(1). 内联缓存:缓存对象属性的访问路径;
(2). 逃逸分析:优化内存分配;
(3). 去优化:如果优化假设不成立,回退到解释器执行;
3. 创建执行上下文(Execution Context)
执行上下文是 JavaScript 代码执行的环境,分为三种类型:
1. 全局执行上下文:代码首次运行时创建,只有一个;
2. 函数执行上下文:每次函数调用时创建;
3. Eval 执行上下文:在 eval 函数中执行代码时创建(较少使用);
3.1. 执行上下文的组成
每个执行上下文包含以下内容:
1. 变量环境(Variable Environment):存储变量和函数声明;
2. 词法环境(Lexical Environment):用于作用域链的实现;
3. this 绑定:指向当前上下文的对象;
3.2. 执行上下文的生命周期
1. 创建阶段
(1). 创建变量对象(VO),初始化变量和函数声明;
(2). 建立作用域链;
(3). 确定 this 的值;
2. 执行阶段
(1). 逐行执行代码;
(2). 对变量赋值,执行函数调用;
4. 执行(Execution)
在代码执行阶段,JavaScript 引擎会逐行执行代码,并处理以下内容:
4.1. 变量和函数声明
1. 变量提升(Hoisting)
变量声明会被提升到作用域顶部,但赋值不会。
console.log(x); // undefined
var x = 10;
以上代码实际执行顺序:
var x;
console.log(x); // undefined
x = 10;
2. 函数提升
函数声明会被整体提升。
foo(); // "Hello"
function foo() {
console.log("Hello");
}
4.2. 代码执行
同步代码:逐行执行。
异步代码:通过事件循环处理。
5. 事件循环(Event Loop)
事件循环是 JavaScript 处理异步操作的核心机制。它由以下部分组成:
5.1. 调用栈(Call Stack)
作用:用于跟踪函数调用,后进先出(LIFO)
function foo() {
console.log("foo");
}
function bar() {
foo();
}
bar();
调用栈过程:
1. bar() 入栈;
2. foo() 入栈;
3. console.log("foo") 入栈并执行;
4. foo() 出栈;
5. bar() 出栈;
5.2. 任务队列(Task Queue)
作用:存储异步任务的回调函数(如 setTimeout
、setInterval
)。
执行时机:调用栈为空时,事件循环从任务队列中取出任务执行。
5.3. 微任务队列(Microtask Queue)
作用:存储高优先级的异步任务回调(如 Promise
、MutationObserver
)。
执行时机:在每个任务执行完成后,立即执行所有微任务。
5.4. 事件循环流程
1. 执行同步代码;
2. 检查微任务队列并执行所有微任务;
3. 检查任务队列并执行一个任务;
4. 重复上述过程;
6. 垃圾回收(Garbage Collection)
JavaScript 引擎会自动管理内存,回收不再使用的对象。主要算法包括:
6.1. 标记清除(Mark-and-Sweep)
步骤:从根对象(如全局对象)开始,标记所有可达对象。清除未标记的对象。
优点:能处理循环引用。
6.2. 引用计数(Reference Counting)
原理:记录每个对象的引用次数,当引用次数为 0 时回收。
缺点:无法处理循环引用。
6.3. 分代回收(Generational Collection)
原理:将内存分为新生代和老生代,分别采用不同的回收策略。
优点:提高回收效率。
7. 总结
JavaScript 的执行过程是一个复杂的机制,涉及解析、编译、执行上下文、事件循环和垃圾回收等多个阶段。理解这些细节有助于编写高效、可靠的代码,并更好地调试和优化性能。