手把手教会你JavaScript引擎如何执行JavaScript代码

手把手教会你JavaScript引擎如何执行JavaScript代码
最新回答
屋顶上的小猫咪

2021-11-23 20:34:04

JavaScript 引擎执行代码的过程可分为语法分析、编译、执行三个阶段,其核心机制围绕执行上下文的创建(包括变量对象、作用域链、this 指向)展开。以下是详细步骤说明:

一、语法分析阶段
  • 作用:对代码进行词法分析、语法分析、语义分析,生成抽象语法树(AST)。
  • 关键行为:检查语法错误(如缺少括号、关键字拼写错误),若发现错误则抛出异常并终止执行。
  • 示例:代码 let a = ; 会因缺少赋值表达式触发语法错误。
二、编译阶段:创建执行上下文

每进入一个新运行环境(如全局、函数、eval),引擎会创建对应的执行上下文,包含以下核心操作:

1. 创建变量对象(VO)
  • 全局环境:变量对象为 window(浏览器)或 global(Node.js)。
  • 函数环境

    创建 arguments 对象(记录参数值)。

    处理函数声明和变量声明:

    函数声明:直接初始化函数对象(函数提升优先于变量提升)。

    变量声明:初始化为 undefined(赋值操作在执行阶段进行)。

  • ES6 改进:通过词法环境(Lexical Environment)和变量环境(Variable Environment)分别记录 let/const/class 和 var/function 声明,实现块级作用域。
2. 建立作用域链
  • 结构:由当前词法环境的环境记录(记录变量)和外部词法环境引用(指向外层作用域)组成。
  • 查询规则:从内到外逐层查找变量,直到外层引用为 null(全局作用域)。
  • 示例:function foo() { console.log(a); // 查找顺序:foo → 全局 var a = 1;}
3. 确定 this 指向
  • 规则

    全局环境:指向 window(浏览器)或 global(Node.js)。

    函数调用

    作为对象方法调用时,指向调用对象(如 obj.method() 中的 obj)。

    使用 new 调用时,指向新创建的对象。

    箭头函数继承外层 this。

    通过 call/apply/bind 显式绑定。

    类构造函数:指向新实例对象。

  • 示例:const obj = { name: "Alice", sayHi() { console.log(this.name); } };obj.sayHi(); // 输出 "Alice"
三、执行阶段
  1. 激活变量对象:将编译阶段的 VO 转为活动对象(AO),完成变量赋值和参数传递。

    AO 组成:AO = VO + 函数参数 + arguments。

    示例

    function foo(a) { var b = 2; function c() {}}foo(1); // 执行时 AO: { a: 1, b: 2, c: function, arguments: { 0: 1, length: 1 } }
  2. 执行代码:按顺序执行语句,通过作用域链访问变量。

  3. 销毁上下文:函数执行完毕后,其执行上下文(含作用域链和 AO)被销毁,但闭包会保留变量对象。

四、闭包与内存管理
  • 闭包定义:函数能访问其定义时的作用域链,即使函数在其他环境中执行。
  • 示例:function outer() { let count = 0; return function inner() { count++; return count; };}const fn = outer();console.log(fn()); // 1(outer 的变量对象未被销毁)
  • 内存泄漏风险:闭包保留的变量若未及时清理,可能导致内存占用过高。
五、完整流程示例function example(a) { var b = 2; function inner() { return a + b; } return inner;}const result = example(1);console.log(result()); // 输出 3
  1. 编译阶段

    全局环境创建 example 函数。

    调用 example(1) 时,创建函数执行上下文:

    VO: { a: undefined, b: undefined, inner: function }。

    作用域链: [example环境, 全局环境]。

    this 指向全局对象。

  2. 执行阶段

    AO 更新为 { a: 1, b: 2, inner: function }。

    inner 函数通过作用域链访问 a 和 b。

  3. 闭包保留:result 引用 inner,导致 example 的变量对象未被销毁。

通过理解上述过程,可精准定位 this 指向错误、变量意外修改等常见问题,并合理利用闭包实现模块化设计。