JavaScript引擎运行机制详解:从全局初始化到闭包执行

作者:闭包旅人

时间:2026-10-30

浏览:256

本文深入解析JavaScript引擎从代码定义到执行的全过程。通过全局初始化、函数A执行及函数B执行三个阶段,详细阐述执行环境栈、变量对象、活动对象及作用域链的构建与交互机制,并结合闭包示例说明标识符解析规则,最后探讨基于变量查找机制的性能优化思路。

文章目录

JavaScript引擎运行机制概述

JavaScript从代码定义到最终执行,JS引擎在底层会执行大量初始化操作。因此,在深入理解JS引擎工作机制之前,必须掌握几个核心概念:执行环境栈、全局对象、执行环境、变量对象、活动对象、作用域以及作用域链。这些概念构成了JS引擎工作的基石。本文旨在通过一个具体的Demo案例,系统梳理JS引擎从定义到执行的完整细节,并解析上述概念在其中的具体作用,而非孤立地讲解单个概念。


var x = 1; //定义一个全局变量 x
function A(y){
  var x = 2; //定义一个局部变量 x
  function B(z){ //定义一个内部函数 B
    console.log(x+y+z);
  }
  return B; //返回函数B的引用
}
var C = A(1); //执行A,返回B
C(1); //执行函数B 

该Demo涉及闭包,最终执行结果为4。接下来,我们将分三个阶段深入剖析JS引擎的工作机制:全局初始化、执行函数A、以及执行函数B。

全局初始化阶段

一、全局初始化 当JS引擎开始执行一段代码时,需完成三项初始化任务: 首先,创建全局对象(Global Object)。该对象在整个应用程序生命周期中唯一存在,其属性可被全局访问。初始化时,引擎会将Math、String、Date、document等常用JS内置对象作为其属性。由于全局对象无法直接通过名称访问,引擎还创建了一个名为window的属性指向该全局对象,从而允许通过window进行访问。伪代码模拟的全局对象结构大致如下:


//创建一个全局对象
var globalObject = { 
  Math:{},
  String:{},
  Date:{},
  document:{}, //DOM操作
  ...
  window:this //让window属性指向了自身
} 

其次,JS引擎构建执行环境栈(Execution Context Stack),并创建全局执行环境(Execution Context, EC)。随后,将该全局EC压入执行环境栈中。执行环境栈的核心作用是确保程序按正确顺序执行。在JavaScript中,每个函数拥有独立的执行环境;当函数被调用时,其执行环境会被推入栈顶并获得执行权;函数执行完毕后,其环境从栈顶弹出,执行权交还给之前的执行环境。伪代码模拟的执行环境栈与EC关系如下:


var ECStack = []; //定义一个执行环境栈,类似于数组

var EC = {};  //创建一个执行空间,
//ECMA-262规范并没有对EC的数据结构做明确的定义,你可以理解为在内存中分配的一块空间

ECStack.push(EC); //进入函数,压入执行环境
ECStack.pop(EC); //函数返回后,删除执行环境 

最后,JS引擎创建与EC关联的全局变量对象(Variable Object, VO),并将VO指向全局对象。VO不仅包含全局对象原有的属性,还涵盖全局定义的变量x和函数A。在定义函数A时,引擎还会为A添加一个内部属性scope,指向VO。每个函数在定义时都会创建关联的scope属性,该属性始终指向定义时的环境。此时的ECStack结构如下:


ECStack = [  //执行环境栈
  EC(G) = {  //全局执行环境
    VO(G):{ //定义全局变量对象
      ... //包含全局对象原有的属性
      x = 1; //定义变量x
      A = function(){...}; //定义函数A
      A[[scope]] = this; //定义A的scope,并赋值为VO本身
    }
  }
]; 

函数A的执行过程

二、执行函数A 当执行进入A(1)时,JS引擎需完成以下工作: 首先,JS引擎创建函数A的执行环境EC,将其推入执行环境栈顶并获取执行权。此时栈中包含两个执行环境:底部的全局执行环境和顶部的函数A执行环境。接着,JS引擎创建函数A的作用域链(Scope Chain)。在JavaScript中,每个执行环境拥有独立的作用域链用于标识符解析;执行环境创建时,其作用域链初始化为当前运行函数的scope所包含的对象。 随后,JS引擎创建当前函数的活动对象(Activation Object, AO)。AO在此处扮演变量对象的角色,仅在函数语境下称为活动对象(可视为变量对象的一个分支)。AO包含函数的形参、arguments对象、this对象、局部变量及内部函数定义,随后AO被推入作用域链顶端。需注意,在定义函数B时,JS引擎同样为B添加scope属性,指向定义B时的环境,即A的活动对象AO。由于AO位于链表前端,且链表具有首尾相连特性,函数B的scope实际上指向了A的整个作用域链。此时的ECStack结构如下:


ECStack = [  //执行环境栈
  EC(A) = {  //A的执行环境
    [scope]:VO(G), //VO是全局变量对象
    AO(A) : { //创建函数A的活动对象
      y:1,
      x:2, //定义局部变量x
      B:function(){...}, //定义函数B
      B[[scope]] = this; //this指代AO本身,而AO位于scopeChain的顶端,因此B[[scope]]指向整个作用域链
      arguments:[],//平时我们在函数中访问的arguments就是AO中的arguments
      this:window //函数中的this指向调用者window对象
    },
    scopeChain:<AO(A),A[[scope]]> //链表初始化为A[[scope]],然后再把AO加入该作用域链的顶端,此时A的作用域链:AO(A)->VO(G)
  },
  EC(G) = {  //全局执行环境
    VO(G):{ //创建全局变量对象
      ... //包含全局对象原有的属性
      x = 1; //定义变量x
      A = function(){...}; //定义函数A
      A[[scope]] = this; //定义A的scope,A[[scope]] == VO(G)
    }
  }
]; 

函数B的执行与闭包解析

三、执行函数B 函数A执行完毕后,返回B的引用并赋值给变量C。执行C(1)等同于执行B(1),JS引擎需完成以下工作: 首先,与前述步骤类似,JS引擎创建函数B的执行环境EC,推入栈顶并获取执行权。此时栈中包含全局执行环境和函数B的执行环境(B在栈顶,全局在栈底)。注意:函数A返回后,其执行环境从栈中移除,仅保留全局执行环境。接着,创建函数B的作用域链,初始化为函数B的scope所包含的对象,即包含A的作用域链。最后,创建函数B的活动对象AO,将B的形参z、arguments对象和this对象作为AO的属性。此时的ECStack结构如下:


ECStack = [  //执行环境栈
  EC(B) = {  //创建B的执行环境,并处于作用域链的顶端
    [scope]:AO(A), //指向函数A的作用域链,AO(A)->VO(G)
    var AO(B) = { //创建函数B的活动对象
      z:1,
      arguments:[],
      this:window
    }
    scopeChain:<AO(B),B[[scope]]> //链表初始化为B[[scope]],再将AO(B)加入链表表头,此时B的作用域链:AO(B)->AO(A)-VO(G)
  },
  EC(A), //A的执行环境已经从栈顶被删除,
  EC(G) = {  //全局执行环境
    VO:{ //定义全局变量对象
      ... //包含全局对象原有的属性
      x = 1; //定义变量x
      A = function(){...}; //定义函数A
      A[[scope]] = this; //定义A的scope,A[[scope]] == VO(G)
    }
  }
]; 

当函数B执行“x+y+z”时,需对x、y、z三个标识符进行解析,遵循变量查找规则:先查找自身活动对象中是否存在该属性,若存在则停止查找并返回;若不存在,则沿作用域链从顶端依次向下查找,直至找到;若整个作用域链均未找到,则返回“undefined”。从上述分析可知,函数B的作用域链结构为: AO(B)->AO(A)->VO(G) 因此,变量x在AO(A)中被找到,不会继续查找VO(G)中的x;变量y同样在AO(A)中找到;变量z则在自身的AO(B)中找到。故执行结果为:2+1+1=4。

性能优化启示

简单总结 理解JS引擎工作机制后,不应仅停留在概念层面,而应将其作为基础工具,用于优化和改善实际工作中的代码,提高执行效率并产生实际价值。以变量查找机制为例,若代码嵌套较深,每次引用全局变量(如位于作用域链最底端的window和document对象),JS引擎都需遍历整个作用域链,这可能导致性能问题。因此,可围绕此问题开展多项性能优化工作。当然,优化方向不止于此,此处不再赘述,本文旨在抛砖引玉。

by @一像素 2015

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章