上一篇简单介绍了执行上下文,现在来讲讲与执行上下文密切相关的Variable Object;
变量对象的定义:变量对象是一个特殊的对象,并且与执行上下文息息相关,VO(变量对象)里面会存有下列内容:
- variables(var,variableDeclaration,arguments);
- function declarations(FD)
- function formal parameters
以上这些内容都会保存在变量对象中,并且变量对象又是(Execution Context)执行上下文的属性:
ExecutionContext = {
VariableObject : {
//这里保存了var声明的变量,FD,function arguments,以及函数形参
}
}
下面的变量对象都用VO来代替
只有全局上下文中的变量对象允许通过VO的属性名称间接访问(全局对象自身就是变量对象);对于其他的上下文直接去引用变量对象是不可能的,纯粹是一种内部的实现机制。稍后会有说明
当我们声明一个变量或者函数的时候,就会成为VO对象新的属性,并且所声明的变量值会称为新的属性值,for example:
var a = 5;
function foo(x) {
var b = 10;
}
foo(20);
上面说过每一个执行上下文中都会有一个变量对象:
全局执行上下文中的变量对象
VO(globalContext) = {
a: 5;
foo: reference function foo
}
foo函数执行上下文中的变量对象
VO(foo function context) ={
x: 20,
b: 10,
arguments: {
0:20,
length:1,
callee: <reference> function foo
}
}
在不同执行上下文中的变量对象 函数上下文中可以在VO上定义额外的细节(例如arguments)

先来看看全局对象的定义:全局对象在进入任何执行上下文中就会被创建,该对象的属性可以在单一副本程序的任何地方访问到它,全局对象的生命周期是随着程序的运行结束而结束;
全局对象在被创建的时候会初始化包含下列属性,例如Math,String,Date等等;同样的也会创建一个额外的对象指向自身,例如在浏览器对象模型中,会创建一个window对象并且指向global object;在node.js中就是指的global对象
global = {
Math: <...>,
String: <...>,
.....
.....
window: global
}
例如:
var a = 6;
console.log(a) //6
console.log(window.a) //6
console.log(a === window.a) //true
之前已经说过在全局执行上下文中的VO(变量对象)就是全局对象
VO(globalContext) === global;
之前也说过在全局执行上下文中声明的变量,可以间接的通过global object的属性来获得:
var a = 'hello';
console.log(a) //'hello',这是直接在全局上下文中找到的
console.log(window.a) //'hello',这是间接通过global找到的,因为在浏览器中,会有一个window对象,并且还是指向global
在node.js中,可以看到变量是挂载到全局对象global上面的:

函数上下文中的(VO)变量对象是不能直接获得的,就好像在学习原型的时候,[[proto]]属性也是不能直接获取的,只不过浏览器厂商使用__proto__属性来模拟它;当函数被调用时,函数上下文中的VO也成为AO(活动对象)。
VO(functionContext) === AO
当进入函数上下文,AO就会被创建,并且会被arguments属性所初始化,arguments的值指向一个对象。
AO = {
arguments: object
}
其中arguments对象将会包含以下属性:
- callee —— 指向当前函数
- length——当函数调用时,传递过来的参数(实参)的数量;注意这里不是函数定义的时候参数的数量,function.length才是函数定义时候的参数数量
- properties-indexes——就是函数调用时传递过来的值(实参)会以索引—属性的形式展现出来
举例说明:
function foo(a,b,c) {
console.log(arguments)
console.log(arguments.length === 2) //true
console.log(foo.length) //3 注意与上一行进行区分
console.log(arguments.callee === foo) //true
}
foo(1,2);
上下文代码处理过程分为两个阶段
- 进入执行上下文,但是还没有开始执行该上下文中的代码(Entering the execution context,but the executable code has not started execute)
- 上下文中的代码执行阶段 (code execution)
函数上下文和全局上下文都会有这两个阶段,并且相互独立,互不影响。
第一阶段:进入执行上下文,但是还没有开始执行该上下文中的代码 在进入执行上下文,但是还没有开始执行该上下文中的代码的时候,VO会被下列属性所初始化:
- 对于每一个函数的形参(如果进入了函数执行上下文)——函数的形参将会成为VO的属性,形参的值(函数调用时的传递过来的值)就会称为该属性的值,但对于没有传递过来的参数,其形参的值就会被赋予undefined,下列举例说明:
function fn(a,b,c) {
}
foo(1,2)
进入函数执行上下文,但是还没有开始执行函数体里面的代码
VO={
a:1,
b:2,
c: undefined, //在函数调用时,并没有给参数c传递值,那么就会被赋予为undefined
arguments: {
0:1,
1:2,
length:2,
callee: <reference> function fn
}
}
2.对于每一个函数声明,也会保存在VO中,但是之后的同名函数声明会覆盖之前的,也就是说VO中会保存最新的函数声明,下列举例说明:
function foo(a,b) {
console.log(a+b)
}
function foo(a,b,c) {
console.log(a+b+c)
}
当进入全局执行上下文时,VO会进行初始化,第二个同名函数声明会覆盖之前的函数声明
VO = {
foo: reference <function foo(a,b,c) {console.log(a+b+c)}>
}
- 对于每一个变量声明(函数表达式也是变量声明),所声明的变量将会称为VO的属性,其值会被赋予成undefined。但是如果变量名与之前的形参名或者函数声明时的函数名相同,那么之后的变量声明并不会破坏已经存在的属性(也即不会被后面的同名变量所覆盖)
function fn(a,b,c) {
var d = 30;
function foo(){};
var test = function {}
}
fn(1,2);
当进入函数fn的上下文时,VO会进行初始化
VO = {
a: 1,
b: 2,
c: undefined,
foo: reference function foo,
d: undefined,
test: undefined,
arguments: {
0:1,
1:2,
length:2,
callee: <reference> function fn
}
}
注意:如果进入函数上下文中会先检查形参,然后是函数声明(不是函数表达式),最后是其他变量
第二阶段:代码执行阶段 到这里为止,VO已经初始化了一些属性,但是还是有一部分值并不是我们所希望的(初始值被赋予了undefined)。 随着代码的执行,VO中属性的值会逐渐被修改。
上述例子中的VO会被修改成:
VO = {
a: 1,
b: 2,
c: undefined,
foo: reference function foo,
d: 30,
test: <reference> function,
arguments: {
0:1,
1:2,
length:2,
callee: <reference> function fn
}
}
下面举一个经典的例子
alert(a); //function
var a = 1;
alert(a); //1
a = 10;
function a() {};
alert(a); //10
为什么第一个a的值为function,不是1或10或undefined? 分析过程:
- 首先进入全局执行上下文时,会对VO进行初始化,此时由于在全局上下文中(就不会检查形参),会先检查函数声明,接着是变量声明;之前的第三条规则说过如果变量名与之前的形参名或者函数声明时的函数名相同,那么之后的变量声明并不会修改已经存在的属性(也即不会被后面的同名变量所覆盖)
此时VO = {
a: reference function a //这里并不是undefined,不会被覆盖
}
2.代码执行阶段——当上述代码执行到第三行时,VO会被修改成:
VO = {
a: 1
}
故a弹出来为1
3.当代码执行到倒数第二行时,VO会被修改成:
VO = {
a: 10
}
故a弹出来为10
var a = 10;
function fn(){
console.log(a); //10
}
我们都知道a的值打印出来是10,函数内部可以访问函数外部的变量,但是函数外部却不能访问函数内部的变量(闭包除外),但你知道为什么会这样吗?下一篇中的作用域将会为你解释这个疑惑