定义

变量对象 (variable object, AO)

活动对象 (activation object,VO)

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。

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

活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object ,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

∵ VO = 变量声明 + 函数声明
∵ AO = AO + arguments(实参) + formal parameters(形参)
∴ AO = 变量声明 + 函数声明 + arguments(实参) + formal parameters(形参) // 函数变量对象

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

ECMAScript规范指出独立作用域只能通过“函数(function)”代码类型的执行上下文创建。

变量对象

变量对象(VO)是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:

  • 变量声明
  • 函数声明
  • 函数的形参

全局上下文中的变量对象就是全局对象

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

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。

活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

只有全局上下文的变量对象允许通过VO的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象),在其它上下文中是不能直接访问VO对象的,因为它只是内部机制的一个实现。

如下代码:

  1. var a = 10;
  2. function fn(args) {
  3. var b = 20;
  4. };
  5. test(20);

对应的变量对象如下:

  1. VO(globalContext) = {
  2. a: 10,
  3. fn: <reference function>
  4. }
  5. VO(fnContext) = {
  6. args: 20,
  7. b: 20
  8. }

函数执行上下文生命同期

在函数执行上下文中,VO是不能直接访问的,由AO代表VO出现。

执行上下文的代码会分成两个阶段进行处理:创建、执行、回收。

  • 创建阶段
  • 执行阶段
  • 回收阶段

以如下代码为例说明:

  1. function fn(x, y, z) {
  2. var a = 10
  3. var b = 20
  4. c = 30
  5. function b() {}
  6. var sum = function() {}
  7. }
  8. fn(11, 22)

创建阶段

创建变量对象VO,并进行以下动作进行初始化:

  1. 处理函数的形参
    1. arguments 初始化变量对象属性,arguments 属性的值是 Arguments 对象。
    2. 没有实参,对应的形参的值是 undefined
  2. 函数声明
    1. 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建。
    2. 如变量对象已经存在相同名称的属性,则完全替换这个属性。
  3. 变量声明
    1. 由名称和对应值(undefined)组成一个变量对象的属性被创建。
    2. 如变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

到此,VO 变量对象如下:

  1. VO(fn) = {
  2. arguments: {
  3. 0: 11,
  4. 1: 22,
  5. length: 2
  6. },
  7. x: 11,
  8. y: 22,
  9. z: undefined,
  10. a: undefined,
  11. b: <reference function b>,
  12. sum: undefined
  13. }

执行阶段

进入执行阶段后,VO 变 AO,且 AO 可访问,在执行行对 AO 进行改变,如赋值操作等。

此时 AO 如下:

  1. AO(fn) = {
  2. arguments: {
  3. 0: 11,
  4. 1: 22,
  5. length: 2
  6. },
  7. x: 11,
  8. y: 22,
  9. z: undefined,
  10. a: 10, // 在执行到“var a = 10”时,修改该属性
  11. b: <reference function b>,
  12. sum: <reference function sum> // 同上
  13. }

再来看一段代码:

  1. function fn() {
  2. console.log(x) // function x() {}
  3. var x = 10
  4. console.log(x) // 10
  5. x = 20
  6. function x() {}
  7. console.log(x) // 20
  8. }

解析:

  1. AO 中因为函数声明优先于变量声明,得 VO(fn).x = function() {} 所以第一个 xfunction x() {}
  2. 接着执行得 VO(fn).x = 10,所以第二个 x10
  3. 继续执行得 VO(fn).x = 10,所以第三个 x20