变量对象VO与活动对象AO

变量对象VO

变量对象VO是与执行上下文相关的特殊对象,用来存储上下文的函数声明,函数形参和变量。在global全局上下文中,变量对象也是全局对象自身,在函数上下文中,变量对象被表示为活动对象AO。

变量对象VO存储上下文中声明的以下内容 

函数声明FD(如果在函数上下文中),—-不包含函数表达式 
函数形参function arguments, 
变量声明–注意b=10不是变量,但是var b = 10;是变量,有变量声明提升

var a = 10;

function test(x) {
var b = 20;
}; test(30); // 全局上下文的变量对象
VO(globalContext) = {
a: 10,
test: <reference to function>
}; // test函数上下文的变量对象
VO(test functionContext) = {
x: 30,
b: 20
};

  变量对象VO分类

全局上下文的变量对象VO,函数上下文的变量对象VO。

//全局上下文的变量对象VO就是全局对象GlobalContextVO

VO(globalContext) === global;

(VO === this === global)

函数上下文变量对象FunctionContextVO  

 (VO === AO, 并且添加了<arguments>(形参类数组)和<formal parameters>(形参的值))

  

  活动变量AO

在函数执行上下文中,VO是不能直接访问的,此时由活动对象AO扮演VO的角色

当函数被调用后,这个特殊的活动对象就被创建了。它包含普通参数与特殊参数对象(具有索引属性的参数映射表)。活动对象AO在函数上下文中作为变量对象VO使用。

Arguments对象是活动对象的一个属性,它包括如下属性:

callee — 指向当前函数的引用

length — 真正传递的参数个数

properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 
properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。

function foo(x, y, z) {

     console.log(foo.name); //foo
// 声明的函数参数数量arguments (x, y, z)
console.log(foo.length); // // 真正传进来的参数个数(only x, y)
console.log(arguments.length); // // 参数的callee是函数自身
console.log(arguments.callee === foo); // true // 参数共享 console.log(x === arguments[0]); // true
console.log(x); // arguments[0] = 20;
console.log(x); // x = 30;
console.log(arguments[0]); // // 不过,没有传进来的参数z,和参数的第3个索引值是不共享的 z = 40;
console.log(arguments[2]); // undefined arguments[2] = 50;
console.log(z); // } foo(10, 20);

处理上下文代码的2个阶段

进入执行上下文和执行代码

进入执行上下文: 
变量是进入上下文阶段放入VO中,也就是变量声明提升并且变量声明顺序上是在函数声明和形参声明后

//变量声明在顺序上跟在函数声明和形式参数声明之后,而且在这个进入上下文阶段,变量声明不会干扰VO中已经存在的同名函数声明或形式参数声明
alert(x); // function var x = 10;
alert(x); // 10 x = 20; function x() {}; alert(x); // 20

  

alert(x); // 20

var x = 10;
alert(x); // 10 x = 20; function b(x) {}; alert(x); // 20

  

function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
} test(10); // call
当进入带有参数10的test函数上下文时,AO表现为如下:
//AO里并不包含函数“x”。这是因为“x” 是一个函数表达式(FunctionExpression, 缩写为 FE) 而不是函数声明,函数表达式不会影响VO
AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <reference to FunctionDeclaration "d">
e: undefined
};

  

//函数表达式不在AO中
console.log(x) // x is not defined
(function x() {});

特例

var x =10;
function foo() { var y = 20; function barFD() { // 函数声明
alert(x);
alert(y);
} var barFE = function () { // 函数表达式
alert(x);
alert(y);
}; var barFn = Function('alert(x); alert(y);'); barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined } foo();

  我们看到,通过Function函数构造函数(Function constructor)创建的函数“bar”,是不能访问变量“y”的。但这并不意味着函数“barFn”没有[[scope]]属性(否则它不能访问到变量“x”)。问题在于通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象(如果再全局设置y值才可以访问)。考虑到这一点,如通过这种函数创建除全局之外的最上层的上下文闭包是不可能的。

执行上下文:
 a:定义:

  每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分。
活动的执行上下文组在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(global context),而顶部就是当前(活动的)执行上下文。堆栈在EC类型进入和退出上下文的时候被修改(进栈或出栈)。
b:若我们定义执行上下文堆栈是一个数组:
ECStack = [];
//GlobalContext始终在堆栈底部,其余的FunctionContext按激活顺序被压入,结束时被弹出
ECStack = [
  globalContext
];
ECStack = [  FunctionAaContext,//函数A内部函数a可执行代码 
FunctionAContext,//函数A可执行代码(不包含内部函数代码,只能对其声明,就像在全局中只能对函数A声明一样,需要内部函数激活时,创建   GlobalContext 出新的执行上下文环境,才能执行相应的代码)      调用结束后出栈  
];
   ps:
每次进入function (即使function被递归调用或作为构造函数) 的时候或者内置的eval函数工作的时候,当前的执行上下文都会被压入堆栈;

全局执行上下文做了这些工作:

  • 变量、函数表达式——变量声明,默认赋值为undefined;
  • this——赋值;
  • 函数声明——赋值

如果代码段是函数体,那么在此基础上需要附加:

  • 参数赋值
  • arguments赋值
  • *变量的取值作用域

给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

现在让我们来串一下

1、全局执行上下文

创建global.VO

2、全局变量的赋值 | 调用函数()(激活)

激活函数后,会得到当前的AO,其中有内部函数的声明、内部变量的声明、形参

3、进入所激活的函数的上下文

进行所在函数上的变量的赋值 各种运算 (作用域链包含全局的VO,和当前执行上下文的AO)

4.a、若在函数中有内部函数调用(或自执行),重复3;

4.b  若返回一个函数(或其引用),且该函数有对*变量的引用-->形成闭包-->作用域链机制依然有效-->当前已压入执行上下文堆栈的FunctionContext不会出栈;-->回到2;

4.c  正常return或正常结束,FunctionContext出栈;-->回到2;

5.所有代码执行完毕,程序关闭,释放内存。

  

上一篇:JS中OOP之模拟封装和继承和this指向详解


下一篇:ThinkPHP讲解(十一)——验证码和文件上传