EC+VO+SCOPE for ES3

词法环境

词法作用域

词法作用域(lexcical scope)。即JavaScript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码。

词法环境

用于定义特定变量和函数标识符在ECMAScript代码的词法嵌套结构上的关联关系, 一个词法环境由一个环境记录项和可能为空的外部词法环境引用构成

词法环境 = 词法环境记录项 + 外部词法环境
外部词法环境是包含内部词法环境的词法环境, 外部词法环境可能有多个内部词法环境

环境记录项 = 声明式环境记录项 || 对象式环境记录项

执行环境

  • javascript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)

  • 执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构(scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构。 varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中

  • 函数实例执行时,会将这些信息从语法树复制到scriptObject上

Executable Code and Execution contents

“执行上下文”可以看做当前代码的运行环境或者作用域。

Types of Executable Code

  • Global Code:全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。

  • Function Code: 函数级别的代码 – 当执行一个函数时,运行函数体中的代码。

  • Eval Code: 在Eval函数内运行的代码,在特定的一次对 eval 的调用过程中,eval 代码作为该程序的 Global Code 部分。

每当调用执行一个函数时,引擎就会自动新建出一个函数上下文, 函数中函数也可能调用另一个函数,这样又创建一个执行环境, 也被称为上下文堆栈

执行上下文堆栈

  • ECMAScript的程序执行都可以看做是一个执行上下文堆栈[execution context (EC) stack]。堆栈的顶部就是处于激活状态的上下文, 堆栈最底部即为全局执行上下文环境[global execution context];

  • 激活其它上下文的某个上下文被称为 调用者(caller) 。被激活的上下文被称为被调用者(callee) 。被调用者同时也可能是调用者(比如一个在全局上下文中被调用的函数调用某些自身的内部方法)。

  • 当一个caller激活了一个callee,那么这个caller就会暂停它自身的执行,然后将控制权交给这个callee. 于是这个callee被放入堆栈,称为进行中的上下文[running/active execution context]. 当这个callee的上下文结束之后,会把控制权再次交给它的caller,然后caller会在刚才暂停的地方继续执行。在这个caller结束之后,会继续触发其他的上下文。一个callee可以用返回(return)或者抛出异常(exception)来结束自身的上下文。

执行上下文的建立过程

每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:

  1. 建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)

    • 建立变量,函数,arguments对象,参数
    • 建立作用域链
    • 确定this的值
  2. 代码执行阶段:

    • 变量赋值,函数引用,执行其它代码

实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

executionContextObj = {
    variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
    scopeChain: {   /* variableObject 以及所有父执行上下文中的variableObject */ },
    this: {}
 }

变量对象(variable object)

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

  1. 变量 (var, 变量声明)
  2. 函数声明 (FunctionDeclaration, 缩写为FD);
  3. 函数的形参 注: 只有全局上下文的变量对象允许通过VO的属性名称来间接访问

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

对于所有类型的执行上下文来说,变量对象的一些操作(如变量初始化)和行为都是共通的。从这个角度来看,把变量对象作为抽象的基本事物来理解更为容易。同样在函数上下文中也定义和变量对象相关的额外内容。

抽象变量对象VO (变量初始化过程的一般行为)

  1. 全局上下文变量对象GlobalContextVO

    (VO === this === global), VO:

    • 所有函数声明(FunctionDeclaration, FD)
    • 所有变量声明(var, VariableDeclaration)
  2. 函数上下文变量对象FunctionContextVO

    (VO === AO, 并且添加了arguments和形参), AO:

    • 普通参数(formal parameters) 与特殊参数(arguments)对象
    • 所有函数声明(FunctionDeclaration, FD)
    • 所有变量声明(var, VariableDeclaration)
  3. eval上下文

    • eval会使用全局变量对象或调用者的变量对象(eval的调用来源)
    • 变量声明在顺序上跟在函数声明和形式参数声明之后,但不会干扰AO中已经存在的同名函数声明或形式参数声明
    • (function x() {}); 类似这样的函数表达式并不会影响AO
    • 不管是使用var关键字(在全局上下文)还是不使用var关键字(在任何地方),都可以声明一个变量”。 请记住,这是错误的概念; 任何时候,变量只能通过使用var关键字才能声明。

变量的特性

  1. 变量有一个特性(attribute):{DontDelete},这个特性的含义就是不能用delete操作符直接删除变量属性
  2. eval上下文,变量没有{DontDelete}特性, 使用一些调试工具(例如:Firebug)的控制台测试该实例时,请注意,Firebug同样是使用eval来执行控制台里你的代码。因此,变量属性同样没有{DontDelete}特性,可以被删除。

作用域

javascript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,所以只能说javascript的作用域机制非常接近词法作用域(lexical scope)

作用域链

在ECMAScript中,会用到内部函数[inner functions],在这些内部函数中,我们可能会引用它的父函数变量,或者全局的变量。我们把这些变量对象成为上下文作用域对象[scope object of the context]. 类似于上面讨论的原型链[prototype chain],我们在这里称为作用域链[scope chain]

作用域链与一个执行上下文相关, 用于在标识符解析中变量查找。 标示符[Identifiers]可以理解为变量名称、函数声明和普通参数

函数上下文的作用域链在函数调用时创建的,包含活动对象和这个函数内部的[[scope]]属性。其scope定义如下:Scope = AO + [[Scope]]

函数在被创建时保存外部作用域,是因为这个 被保存的作用域链(saved scope chain) 将会在未来的函数调用中用于变量查找。这种形式的作用域称为静态作用域[static/lexical scope]

在上下文中示意如下:

activeExecutionContext = {
    VO: {...}, // or AO
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
      // for identifiers lookup
    ]
};

 

var x = 10;

function foo() {
  alert(x);
}

(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

  

在标识符解析过程中,使用函数创建时定义的词法作用域--变量解析为10,而不是20。此外,这个例子也清晰的表明,一个函数(这个例子中为从函数“foo”返回的匿名函数)的[[scope]]持续存在,即使是在函数创建的作用域已经完成之后。

补充说明

  1. 通过构造函数创建的函数的[[scope]]属性总是唯一的全局对象

  2. 在代码执行阶段有两个声明能修改作用域链。这就是with声明和catch语句

  3. 在代码执行过程中,如果使用with或者catch语句就会改变作用域链。而这些对象都是一些简单对象,他们也会有原型链。这样的话,作用域链会从两个维度来搜寻。

  4. 在解释执行阶段, 遇到变量需要解析时,会首先从当前执行环境的活动对象中查找, 如果没有找到而且该执行环境拥有者有prototype属性时, 则会从prototype链中查找, 否则将会按照作用域链查找;

end!

posted @ 2013-09-18 23:11  mininice  阅读(197)  评论(0编辑  收藏  举报