Javascript 引擎工作机制详解


Posted in Javascript onNovember 30, 2016

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,下面我们将分全局初始化、执行函数A、执行函数B 三个阶段来分析JS引擎的工作机制:

一、全局初始化

JS引擎在进入一段可执行的代码时,需要完成以下三个初始化工作:

首先,创建一个全局对象(Global Object) , 这个对象全局只存在一份,它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期。全局对象在创建时,将Math,String,Date,document 等常用的JS对象作为其属性。由于这个全局对象不能通过名字直接访问,因此还有另外一个属性window,并将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关联的全局变量对象(Varibale Object) VO,  并把VO指向全局对象,VO中不仅包含了全局对象的原有属性,还包括在全局定义的变量x 和函数 A,与此同时,在定义函数A的时候,还为 A 添加了一个内部属性scope,并将scope指向了VO。每个函数在定义的时候,都会创建一个与之关联的scope属性,scope总是指向定义函数时所在的环境。此时的ECStack结构如下:

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

二、 执行函数A

当执行进入A(1) 时,JS引擎需要完成以下工作:

首先,JS引擎会创建函数A的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权。此时执行环境栈中有两个执行环境,分别是全局执行环境和函数A执行环境,A的执行环境在栈顶,全局执行环境在栈的底部。然后,创建函数A的作用域链(Scope Chain) ,在javascript中,每个执行环境都有自己的作用域链,用于标识符解析,当执行环境被创建时,它的作用域链就初始化为当前运行函数的scope所包含的对象。 

接着,JS引擎会创建一个当前函数的活动对象(Activation Object) AO,这里的活动对象扮演着变量对象的角色,只是在函数中的叫法不同而已(你可以认为变量对象是一个总的概念,而活动对象是它的一个分支), AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义,然后AO会被推入作用域链的顶端。需要注意的是,在定义函数B的时候,JS引擎同样也会为B添加了一个scope属性,并将scope指向了定义函数B时所在的环境,定义函数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

函数A被执行以后,返回了B的引用,并赋值给了变量C,执行 C(1) 就相当于执行B(1),JS引擎需要完成以下工作:

首先,还和上面一样,创建函数B的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权。 此时执行环境栈中有两个执行环境,分别是全局执行环境和函数B的执行环境,B的执行环境在栈顶,全局执行环境在栈的底部。(注意:当函数A返回后,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引擎的工作机制之后,我们不能只停留在理解概念的层面,而要将其作为基础工具,用以优化和改善我们在实际工作中的代码,提高执行效率,产生实际价值才是我们的真正目的。就拿变量查找机制来说,如果你的代码嵌套很深,每引用一次全局变量,JS引擎就要查找整个作用域链,比如处于作用域链的最底端window和document对象就存在这个问题,因此我们围绕这个问题可以做很多性能优化的工作,当然还有其他方面的优化,此处不再赘述,本文仅当作抛砖引玉吧!

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

Javascript 相关文章推荐
对JavaScript的eval()中使用函数的进一步讨论
Jul 26 Javascript
背景图跟随鼠标移动的Mootools插件实现代码
Dec 12 Javascript
Javascript级联下拉菜单以及AJAX数据验证核心代码
May 10 Javascript
js中settimeout方法加参数
Feb 28 Javascript
jQuery使用post方法提交数据实例
Mar 25 Javascript
javascript实现复选框选中属性
Mar 25 Javascript
js网页滚动条滚动事件实例分析
May 05 Javascript
jQuery实现高亮显示网页关键词的方法
Aug 07 Javascript
Form表单按回车自动提交表单的实现方法
Nov 18 Javascript
javascript html5轻松实现拖动功能
Mar 01 Javascript
webpack-dev-server自动更新页面方法
Feb 22 Javascript
js实现简单分页导航栏效果
Jun 28 Javascript
将JSON字符串转换成Map对象的方法
Nov 30 #Javascript
JS实现重新加载当前页面或者父页面的几种方法
Nov 30 #Javascript
JS实现重新加载当前页面
Nov 29 #Javascript
jQuery弹出div层过2秒自动消失
Nov 29 #Javascript
headjs实现网站并行加载但顺序执行JS
Nov 29 #Javascript
如何使用headjs来管理和异步加载js
Nov 29 #Javascript
JS针对Array的各种操作汇总
Nov 29 #Javascript
You might like
CodeIgniter错误mysql_connect(): No such file or directory解决方法
2014/09/06 PHP
如何在标题栏显示框架内页面的标题
2007/02/03 Javascript
js常用代码段整理
2011/11/30 Javascript
extjs3 combobox取value和text案例详解
2013/02/06 Javascript
javascript之typeof、instanceof操作符使用探讨
2013/05/19 Javascript
jquery indexOf使用方法
2013/08/19 Javascript
JS中Location使用详解
2015/05/12 Javascript
jQuery实现hover合成事件的方法
2015/08/06 Javascript
bootstrap select插件封装成Vue2.0组件
2017/04/17 Javascript
关于Ajax的原理以及代码封装详解
2017/09/08 Javascript
JS设计模式之惰性模式(二)
2017/09/29 Javascript
Express下采用bcryptjs进行密码加密的方法
2018/02/07 Javascript
vue-vuex中使用commit提交mutation来修改state的方法详解
2018/09/16 Javascript
js根据json数据中的某一个属性来给数据分组的方法
2018/10/08 Javascript
Vue 引入AMap高德地图的实现代码
2019/04/29 Javascript
微信小程序事件流原理解析
2019/11/27 Javascript
React倒计时功能实现代码——解耦通用
2020/09/18 Javascript
如何实现小程序与小程序之间的跳转
2020/11/04 Javascript
python检测服务器是否正常
2014/02/16 Python
Python中非常实用的一些功能和函数分享
2015/02/14 Python
python实现根据月份和日期得到星座的方法
2015/03/27 Python
用Python给文本创立向量空间模型的教程
2015/04/23 Python
python3+PyQt5重新实现QT事件处理程序
2018/04/19 Python
python判断设备是否联网的方法
2018/06/29 Python
对python sklearn one-hot编码详解
2018/07/10 Python
Python从文件中读取数据的方法讲解
2019/02/14 Python
Pandas之ReIndex重新索引的实现
2019/06/25 Python
Python考拉兹猜想输出序列代码实践
2019/07/05 Python
如何使用Python多线程测试并发漏洞
2019/12/18 Python
tf.concat中axis的含义与使用详解
2020/02/07 Python
python高阶函数map()和reduce()实例解析
2020/03/16 Python
澳大利亚百货公司:David Jones
2018/02/08 全球购物
给交警的表扬信
2014/01/12 职场文书
元旦晚会策划方案
2014/02/18 职场文书
早会开场白台词大全
2015/06/01 职场文书
NodeJs使用webpack打包项目的方法详解
2022/02/28 NodeJs