Javascript执行流程细节原理解析


Posted in Javascript onMay 14, 2020

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,输出 4

下面我们将分全局初始化、执行函数A、执行函数B 三个阶段来分析JS引擎的工作机制:

1. 全局初始化

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本身
    }
  }
];

2. 执行函数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)
    }
  }
];

3. 执行函数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.

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

Javascript 相关文章推荐
jQuery的实现原理的模拟代码 -3 事件处理
Aug 03 Javascript
10个基于Jquery的幻灯片插件教程
Oct 29 Javascript
JavaScript取得键盘按下方向键是哪个的方法
Aug 04 Javascript
javascript+css3 实现动态按钮菜单特效
Feb 06 Javascript
JavaScript与jQuery实现的闪烁输入效果
Feb 18 Javascript
jquery 遍历数组 each 方法详解
May 25 Javascript
node.js连接MongoDB数据库的2种方法教程
May 17 Javascript
vue登录路由验证的实现
Dec 13 Javascript
JavaScript栈和队列相关操作与实现方法详解
Dec 07 Javascript
Vue实现类似Spring官网图片滑动效果方法
Mar 01 Javascript
小程序关于请求同步的总结
May 05 Javascript
vscode中Vue别名路径提示的实现
Jul 31 Javascript
使用npm命令提示: 'npm' 不是内部或外部命令,也不是可运行的程序的处理方法
May 14 #Javascript
javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法
May 14 #Javascript
vue组件系列之TagsInput详解
May 14 #Javascript
ant-design-vue按需加载的坑的解决
May 14 #Javascript
JavaScript数组排序功能简单实现
May 14 #Javascript
Typescript3.9 常用新特性一览(推荐)
May 14 #Javascript
Node.js API详解之 Error模块用法实例分析
May 14 #Javascript
You might like
五个PHP程序员工具
2008/05/26 PHP
PHP获取当前日期所在星期(月份)的开始日期与结束日期(实现代码)
2013/06/18 PHP
php实现查询百度google收录情况(示例代码)
2013/08/02 PHP
php获取CSS文件中图片地址并下载到本地的方法
2014/12/02 PHP
PHP 反射(Reflection)使用实例
2015/05/12 PHP
kindeditor 加入七牛云上传的实例讲解
2017/11/12 PHP
Nigma vs Alliance BO5 第四场2.14
2021/03/10 DOTA
Javascript+XMLHttpRequest+asp.net无刷新读取数据库数据
2009/08/09 Javascript
一步一步制作jquery插件Tabs实现过程
2010/07/06 Javascript
JavaScript之appendChild、insertBefore和insertAfter使用说明
2010/12/30 Javascript
jquery获取焦点和失去焦点事件代码
2013/04/21 Javascript
JS根据key值获取URL中的参数值及把URL的参数转换成json对象
2015/08/26 Javascript
js小数计算小数点后显示多位小数的实现方法
2016/05/30 Javascript
微信小程序 wx:for的使用实例详解
2017/04/27 Javascript
js获取地址栏参数的两种方法
2017/06/27 Javascript
JavaScript实现元素滚动条到达一定位置循环追加内容
2017/12/28 Javascript
Vue实现固定定位图标滑动隐藏效果
2019/05/30 Javascript
json字符串对象转换代码实例
2019/09/28 Javascript
[01:16:16]DOTA2-DPC中国联赛定级赛 RNG vs Phoenix BO3第二场 1月8日
2021/03/11 DOTA
Python __setattr__、 __getattr__、 __delattr__、__call__用法示例
2015/03/06 Python
python搜索指定目录的方法
2015/04/29 Python
使用Python对IP进行转换的一些操作技巧小结
2015/11/09 Python
python编程开发之类型转换convert实例分析
2015/11/13 Python
python实现下载文件的三种方法
2017/02/09 Python
numpy.random.seed()的使用实例解析
2018/02/03 Python
python爬虫selenium和phantomJs使用方法解析
2019/08/08 Python
python将数据插入数据库的代码分享
2020/08/16 Python
Does C# support multiple inheritance? (C#支持多重继承吗)
2012/01/04 面试题
你的创业计划书怎样才能打动风投
2014/02/06 职场文书
校园安全广播稿范文
2014/09/25 职场文书
消费者投诉书范文
2015/07/02 职场文书
回复函格式及范文
2015/07/14 职场文书
Java基于字符界面的简易收银台
2021/06/26 Java/Android
Python matplotlib可视化之绘制韦恩图
2022/02/24 Python
Python如何让字典保持有序排列
2022/04/29 Python
Python可视化神器pyecharts之绘制箱形图
2022/07/07 Python