JavaScript 的执行过程大致可以分为以下几个阶段:
1. 解析(Parsing)
在这一阶段,JavaScript 引擎会读取源代码,并将其解析成抽象语法树(AST)。抽象语法树是一种深层次的、结构化的代码表达方式,能够以树形结构表现代码中的每个语句、表达式等元素。解析过程中,如果遇到语法错误,会抛出错误,停止进一步执行。
2. 编译(Compilation)
JavaScript 引擎(如V8)通常会将解析后的代码进行即时编译(JIT)。编译器会先生成字节码,这是一种低级的、比源代码更接近机器语言的代码。随后根据程序的执行情况,编译器可能会把热点代码(经常执行的代码)编译成优化的机器码,提高执行效率。
3. 执行(Execution)
编译后得到的字节码或机器码被送到 JavaScript 引擎的执行环境中执行。在执行过程中,会进入下面的子阶段:
- 创建执行上下文(Execution Context):首先会创建全局执行上下文,随后每当调用一个函数时,就会为该函数创建一个新的执行上下文。执行上下文包括变量对象、作用域链和
this
引用等信息。 - 变量提升(Hoisting):在执行代码前,函数声明和变量(声明)会被提升到它们各自的执行上下文的顶部。变量会初始化为
undefined
,而函数则会完整地提升。 - 执行代码(Running Code):按照执行上下文中的代码逐行执行,进行变量赋值、函数调用等。
- 垃圾回收(Garbage Collection):在执行过程中,引擎会进行内存管理,自动释放那些不再被需要的内存空间。
4. 优化(Optimization)
在代码执行的过程中,某些代码可能会被执行多次,引擎会尝试对这些频繁执行的代码进行优化。例如,在V8引擎中,有一个称为“TurboFan”的优化编译器,它可以根据代码执行的特点对代码进行优化,提高性能。如果优化假设失败了(即出现了“去优化” deoptimization),引擎还可以将代码回退到一个较少优化的版本。
5. 回收(Deoptimization & Garbage Collection)
对于那些不再需要的数据和优化,JavaScript 引擎会进行去优化和垃圾回收,以保证内存的高效使用。
例子: 假设我们有这样一个简单的 JavaScript 函数:
javascriptfunction sum(a, b) { return a + b; } let result = sum(5, 3);
首先,该函数会被解析成 AST。然后,它可能会被编译成字节码,当我们调用 sum(5, 3)
时,会创建一个新的执行上下文,包含 a
和 b
的参数以及任何局部变量。在这个上下文中,a
和 b
被赋予了值 5
和 3
,函数执行,并返回结果 8
。这个过程中可能还包括了对 sum
函数的优化,如果函数被频繁调用。最后,当执行上下文离开作用域,如果没有其他引用指向其中的数据,垃圾回收器最终会清理掉这些对象。