执行上下文和变量对象 复习

执行上下文

每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。活动的执行上下文组在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(global context),而顶部就是当前(活动的)执行上下文。堆栈在EC类型进入和退出上下文的时候被修改(推入或弹出)。

可执行代码类型

假设,可执行上下文堆栈是一个数组:

ECStack = [];

每次进入function或eval的时候,这个堆栈会被压入。

1. 全局代码

这种类型的代码是在"程序"级处理的:例如加载外部的js文件或者本地标签内的代码。全局代码不包括任何function体内的代码。 在初始化(程序启动)阶段,ECStack是这样的:

ECStack = [
    globalContext
];

2. 函数代码

当进入funtion函数代码(所有类型的funtions)的时候,ECStack被压入新元素。需要注意的是,具体的函数代码不包括内部函数(inner functions)代码。如下所示,我们使函数自己调自己的方式递归一次:

ECStack = [
    <foo> functionContext
    globalContext
];

每次return的时候,都会退出当前执行上下文的,相应地ECStack就会弹出,栈指针会自动移动位置,这是一个典型的堆栈实现方式。一个抛出的异常如果没被截获的话也有可能从一个或多个执行上下文退出。相关代码执行完以后,ECStack只会包含全局上下文(global context),一直到整个应用程序结束。

3.Eval 代码

eval 代码有点儿意思。它有一个概念: 调用上下文(calling context),例如,eval函数调用的时候产生的上下文。eval(变量或函数声明)活动会影响调用上下文(calling context)。

eval('var x = 10');

(function foo() {
    eval('var y = 20');
})();

alert(x); // 10
alert(y); // "y" 提示没有声明

ECStack的变化过程:

ECStack = [
  globalContext
];

// eval('var x = 10');
ECStack.push(
  evalContext,
  callingContext: globalContext
);

// eval exited context
ECStack.pop();

// foo funciton call
ECStack.push(<foo> functionContext);

// eval('var y = 20');
ECStack.push(
  evalContext,
  callingContext: <foo> functionContext
);

// return from eval
ECStack.pop();

// return from foo
ECStack.pop();

变量对象

变量与执行上下文有关。

var a = 10; // 全局上下文中的变量

(function () {
  var b = 20; // function上下文中的局部变量
})();

alert(a); // 10
alert(b); // 全局变量 "b" 没有声明

变量对象就是变量的数据数据存储和访问机制。

变量对象(缩写为VO)是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容: 1. 变量 (var, 变量声明); 2. 函数声明 (FunctionDeclaration, 缩写为FD); 3. 函数的形参

用一个对象来理解变量对象:

VO = {};

变量对象就是执行上下文的属性:

activeExecutionContext = {
    VO: {
        // 上下文数据(var, FD, function arguments)
    }
};

不同执行上下文的变量对象

抽象变量对象VO (变量初始化过程的一般行为)
  
  ╠══> 全局上下文变量对象GlobalContextVO
          (VO === this === global)
  
  ╚══> 函数上下文变量对象FunctionContextVO
           (VO === AO, 并且添加了<arguments><formal parameters>)

1全局上下文

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

全局对象初始创建阶段将Math、String、Date、parseInt作为自身属性,等属性初始化,同样也可以有额外创建的其它对象作为属性(其可以指向到全局对象自身)。例如,在DOM中,全局对象的window属性就可以引用全局对象自身:

global = {
    Math: <...>,
    String: <...>
    ...
    ...
    window: global //引用自身
};

全局对象不能通过名词直接访问,可以通过全局上下文的this,也可以通过递归调用自身。

String(10); // 就是global.String(10);

// 带有前缀
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;

全局上下文中的变量对象就是全局变量自己。 javascript VO(globalContext) === global;

2函数上下文

在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。

VO(functionContext) === AO;

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

AO = {
    arguments: <ArgO>
};

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

  • callee — 指向当前函数的引用
  • length — 真正传递的参数个数
  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes的值和实际传递进来的参数之间是共享的。
function foo(x, y, z) {

    // 声明的函数参数数量arguments (x, y, z)
    alert(foo.length); // 3

    // 真正传进来的参数个数(only x, y)
    alert(arguments.length); // 2

    // 参数的callee是函数自身
    alert(arguments.callee === foo); // true

    // 参数共享

    alert(x === arguments[0]); // true
    alert(x); // 10

    arguments[0] = 20;
    alert(x); // 20

    x = 30;
    alert(arguments[0]); // 30

    // 不过,没有传进来的参数z,和参数的第3个索引值是不共享的

    z = 40;
    alert(arguments[2]); // undefined

    arguments[2] = 50;
    alert(z); // 40

}

foo(10, 20);

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

  1. 进入执行上下文
  2. 执行代码

进入执行上下文

当进入执行上下文(代码执行之前)时,VO里已经包含了下列属性(前面已经说了):

1.函数的所有形参(如果我们是在函数执行上下文中) — 由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也将被创建。 2.所有函数声明(FunctionDeclaration, FD) —由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变量对象已经存在相同名称的属性,则完全替换这个属性。 3.所有变量声明(var, VariableDeclaration) — 由名称和对应值(undefined)组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}

test(10); // call
//------------------------------------------------------
AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};

代码执行

这个周期内,AO/VO已经拥有了属性(不过,并不是所有的属性都有值,大部分属性的值还是系统默认的初始值undefined )。

AO/VO在代码解释期间被修改如下:

AO['c'] = 10;
AO['e'] = <reference to FunctionExpression "_e">;

例子:

alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {};

alert(x); // 20

为什么第一个alert “x” 的返回值是function,而且它还是在“x” 声明之前访问的“x” 的?为什么不是10或20呢?因为,根据规范函数声明是在当进入上下文时填入的; 同意周期,在进入上下文的时候还有一个变量声明“x”,那么正如我们在上一个阶段所说,变量声明在顺序上跟在函数声明和形式参数声明之后,而且在这个进入上下文阶段,变量声明不会干扰VO中已经存在的同名函数声明或形式参数声明,因此,在进入上下文时,VO的结构如下:

VO = {};

VO['x'] = <reference to FunctionDeclaration "x">

// 找到var x = 10;
// 如果function "x"没有已经声明的话
// 这时候"x"的值应该是undefined
// 但是这个case里变量声明没有影响同名的function的值

VO['x'] = <the value is not disturbed, still function>

紧接着,在执行代码阶段,VO做如下修改:

VO['x'] = 10;
VO['x'] = 20;

javascript复习部分直接阅读并记录汤姆大叔的《深入理解JavaScript系列》 感谢@汤姆大叔指导我学习!



blog comments powered by Disqus

Published

06 March 2014