当前位置: 移动技术网 > IT编程>开发语言>JavaScript > Javascript执行流程细节原理解析

Javascript执行流程细节原理解析

2020年06月14日  | 移动技术网IT编程  | 我要评论

山东警察学院分数线,杭州yy房产网,宋明 高洁

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.

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网