在javascript笔记之JS解释执行过程里说过,当函数准备执行的时候,创建了执行上下文,执行上下文(Execute Context)有创建(预编译
)和执行两个阶段,在创建阶段做了三件事创建变量对象(Variable Object)、建立作用域链(Scope Chain)、确定this指向(this value),也就是说一个执行上下文的数据结构应该是这样的:
1 | EC={ |
今天先来重点了解一下变量对象(VO)
,其他两个先不关注。
变量对象的定义
在我们的程序中会定义很多变量和函数,那么对于解释器来说,它是从哪里找到这些变量和函数的?是的,就是变量对象中。
变量对象(variable object,缩写为VO)是一个抽象的概念,指代与执行上下文相关的特殊对象,是在函数被调用,但是尚未执行的时刻被创建的,这个创建变量对象的过程实际就是函数内数据(函数参数、内部变量、内部函数)初始化的过程。
也就是说变量对象里包含了函数内部的一下内容:
- 内部变量(variables)
- 内部函数(functions)
- 参数列表(arguments)
VO就是执行上下文的一个属性,从数据结构上来说,应该VO是这样的:
1 | VO = { |
为了更好的理解变量对象,下面通过一个例子来探讨下。
1 | function test(a) { |
根据执行上下文的两个阶段,test函数的执行顺序为:
1 | // 1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。 |
通过上面的分析,也就清楚了为什么会有变量提升,这时候的test函数的VO是这样的:
1 | VO = { |
全局上下文中的变量对象
全局对象(Global object)是在进入任何执行上下文之前就已经创建了的对象;这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。
我们上面说的都是函数上下文中的变量对象,是根据执行上下文中的数据(参数、变量、函数)确定其内容的,是不能直接访问VO对象的,因为它只是内部机制的一个实现。
全局上下文中的变量对象则有所不同。以浏览器为例,全局变量对象是window对象,全局上下文在执行前的初始化阶段,全局变量、函数都被挂载在window上。因此全局上下文的变量对象可以通过VO的属性名称来间接访问,而其它上下文不行。
eval的上下文也有所不同,因为eval基本不被使用,忽略
1 | // 以浏览器中为例,全局对象为window |
活动对象
前面我们说过执行上下文有创建和执行两个阶段,当创建阶段后,就进入了执行阶段。在未进入执行阶段之前,变量对象中的属性都是不能访问!但是进入执行阶段之后,变量对象
转变为了活动对象(active object,缩写为AO)
,里面的属性都能被访问了,然后开始进行执行阶段的操作。所以活动对象实际就是变量对象在真正执行时的另一种形式。
如果面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。
总结
在这篇文章里,我们深入学习了跟执行上下文相关的变量对象。通过理解变量对象的创建过程,就是明白解释器是怎么去查找变量和函数,代码的执行顺序是怎样的,也就知道为什么会有变量提升了。