概述

JavaScript 的可执行代码,具有执行上下文,而每个上下文包括以下 3 个属性:

  1. 变量对象(variable object, 简称 VO)
  2. 作用域链(scope chain)
  3. this

变量对象提供了当前环境所需的变量和函数
作用域链用于保证 JS 中变量和函数有序地访问
this 为函数提供了执行者对象

一个上下文的执行周期可以用下图示意:

本文就来介绍执行上下文中的变量对象

那什么是变量对象呢?先看定义:

变量对象是与执行上下文相关的数据作用域,用于存储执行上下文中的变量和函数声明。

不同的执行上下文,变量对象会有一些差别。接下来就分别针对不同的上下文讨论其区别。

一、全局上下文

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

在全局代码的上下文执行环境中,变量对象就是全局对象,在浏览器中,就是 window 对象。

此时,我们可以用 this 和 self 来访问到全局对象,也就是它本身

1
2
console.log(this); // window
console.log(self); // window

其次,全局对象初始创建阶段将 Math、String、Date、parseInt 等函数作为自身方法,还会把全局变量作为自己的属性。

用伪代码表示就是:

1
2
3
4
5
global = {
Math: <...>
Date: <...>
window: global // 引用自身
}

二、函数上下文

我们已经知道,变量对象存储量执行上下文中的函数声明和变量,在函数上下文中,多了arguments(函数参数列表), 一个类数组对象。

用伪代码来表示:

1
2
3
4
5
VO = {
arguments: Arguments,
variables: undefine,
functionName: <Function reference>
}

函数未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象(activation object)。

所以,在函数上下文中,我们将活动对象(activation object)作为变量对象,活动对象最开始只包含一个变量就是 arguments 对象(这个对象是全局环境中没有的)。

arguments 的属性值 Arguments 它包括如下属性:

  • callee — 谁调用了本函数
  • length — 真正传递的参数个数
  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)

三、创建过程

我们再一次来看这个过程图:

创建阶段

全局对象初始化的时候,就将变量对象引用了自身。
而函数的创建却有需要注意的地方。

函数在创建阶段就创建了变量对象
其中,变量对象包括:

  1. 当前函数的参数列表,建立 Arguments 对象。
  2. 所有的函数声明(不包括函数表达式哦!),直接指向函数
  3. 所有的变量声明(var 声明的变量),默认为 undefined

进入执行上下文时,函数声明和变量声明都会提前,这就是声明提升,但是变量声明的值都是 undefined,而函数声明的变量已经可以指向函数。变量声明的优先级最低。

看下面这段代码:

1
2
3
4
5
6
7
8
function foo(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}

foo(10);

当进入函数 foo 时,其变量对象的表现形式为:

1
2
3
4
5
6
7
8
9
10
VO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: undefined,
d: <function reference to d>,
e: undefined,
}

x 是函数表达式,所以不在变量对象当中,e 变量引用的值也是函数表达式,所以变量 e 本身是声明,所以在变量对象当中。

执行阶段

当前进入执行阶段,变量对象激活成活动对象,函数会顺序执行代码,改变变量对象的值:
以上代码就变成:

1
2
3
4
5
6
7
8
9
10
AO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: 10,
d: <reference to function declaration d>,
e: <reference to Function expression to _e>,
}

接下来看一段代码:

1
2
3
4
5
6
7
console.log(foo);

function foo() {
console.log("123");
}

var foo = "456";

以上会打印函数,是因为:

变量优先处理函数声明,再是变量声明。

再看一段代码:

1
2
3
4
5
6
7
if (true) {
var a = 1;
} else {
var b = 2;
}
console.log(a); // 1
console.log(b); // undefined

虽然 else 中的代码永远不会被执行,但是 b 的变量声明在执行之前就默认被设置成 undefined 了。

总结

执行上下文包括三个属性,变量对象,作用域链,this, 不同的执行上下文,变量对象是有区别的。

全局上下文中,变量对象就是本身。

函数上下文中,变量对象包括:arguments, 函数声明,变量声明。在函数创建阶段,变量对象有默认值,进入执行阶段后,变量对象会被激活成活动对象,然后变量对象的值被顺序改变。