javascript笔记之详解变量对象

javascript笔记之JS解释执行过程里说过,当函数准备执行的时候,创建了执行上下文,执行上下文(Execute Context)有创建(预编译)和执行两个阶段,在创建阶段做了三件事创建变量对象(Variable Object)、建立作用域链(Scope Chain)、确定this指向(this value),也就是说一个执行上下文的数据结构应该是这样的:

1
2
3
4
5
EC={
VO: {},
scopeChain:{},
thisValue:{}
};

今天先来重点了解一下变量对象(VO),其他两个先不关注。

变量对象的定义

在我们的程序中会定义很多变量和函数,那么对于解释器来说,它是从哪里找到这些变量和函数的?是的,就是变量对象中。

变量对象(variable object,缩写为VO)是一个抽象的概念,指代与执行上下文相关的特殊对象,是在函数被调用,但是尚未执行的时刻被创建的,这个创建变量对象的过程实际就是函数内数据(函数参数、内部变量、内部函数)初始化的过程。

也就是说变量对象里包含了函数内部的一下内容:

  • 内部变量(variables)
  • 内部函数(functions)
  • 参数列表(arguments)

VO就是执行上下文的一个属性,从数据结构上来说,应该VO是这样的:

1
2
3
4
5
6
7
VO = {
arguments:{},
variable1:undefined,
variable2:undefined,
functionName1:<Function Reference>,
functionName2:<Function Reference>,
}

为了更好的理解变量对象,下面通过一个例子来探讨下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test(a) {
var b = 2;

console.log(foo());

function foo() {
return "function foo";
}

var bar = function(){};

b = 3;
var foo = "foo";
console.log(foo);
}

test(1);

根据执行上下文的两个阶段,test函数的执行顺序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
arguments={
0: 1,
length: 1,
//...
}
// 2. 将所有函数声明放入变量对象中
function foo() {
return "function foo";
}

// 3. 将所有变量声明放入变量对象中,但是因为foo已经存在同名函数,因此此时不会有undefined的赋值
var a = undefined;
var b = undefined;
var bar = undefined;

// 执行阶段需要执行的代码
a=1;
b=2;
console.log(foo());
bar = function(){};
b = 3;
foo = "foo";
console.log(foo);

通过上面的分析,也就清楚了为什么会有变量提升,这时候的test函数的VO是这样的:

1
2
3
4
5
6
7
8
9
10
VO = {
arguments:{
0: 1,
length: 1
},
a:undefined,
b:undefined,
foo: <bar reference> ,// 表示foo的地址引用
bar: undefined
}

全局上下文中的变量对象

全局对象(Global object)是在进入任何执行上下文之前就已经创建了的对象;这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。

我们上面说的都是函数上下文中的变量对象,是根据执行上下文中的数据(参数、变量、函数)确定其内容的,是不能直接访问VO对象的,因为它只是内部机制的一个实现。

全局上下文中的变量对象则有所不同。以浏览器为例,全局变量对象是window对象,全局上下文在执行前的初始化阶段,全局变量、函数都被挂载在window上。因此全局上下文的变量对象可以通过VO的属性名称来间接访问,而其它上下文不行。

eval的上下文也有所不同,因为eval基本不被使用,忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
Window: {
Math: <...>,
String: <...>,
//...
//...
},
VO: Window,
scopeChain: {},
this: Window
}

活动对象

前面我们说过执行上下文有创建和执行两个阶段,当创建阶段后,就进入了执行阶段。在未进入执行阶段之前,变量对象中的属性都是不能访问!但是进入执行阶段之后,变量对象转变为了活动对象(active object,缩写为AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。所以活动对象实际就是变量对象在真正执行时的另一种形式

如果面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。

总结

在这篇文章里,我们深入学习了跟执行上下文相关的变量对象。通过理解变量对象的创建过程,就是明白解释器是怎么去查找变量和函数,代码的执行顺序是怎样的,也就知道为什么会有变量提升了。

参考

变量对象详解
变量对象