【译】深入理解JavaScript系列:2. 变量对象

执行环境与变量对象

在全局执行环境中,全局对象本身就是变量对象,所以可以通过属性名的形式间接的访问变量;

在其他执行环境中,不能直接引用变量对象,变量对象是纯粹的内部实现机制;

变量对象是执行环境对象的属性,执行环境中声明的数据存储在变量对象上,其中包含:

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

声明变量或函数完全等价于在变量对象上以指定名值创建属性;

var a=10;
function test(x){
     var b=20;
}
test(30);

以上代码的变量对象:

//全局环境的VO
VO(globalContext)={
     a:10,
     test: <reference to function>
};
//text函数的执行环境
VO(text functionContext)={
     x:30,
     b:20
};

在具体实现层面以及规范中,变量对象是一个抽象概念,在不同的执行环境中,变量对象具有不同的名称和初始结构;
ES5中变量对象为lexical environment模型取代;

不同执行环境中的变量对象

变量对象的初始化:

  • 全局执行环境的变量对象:
    • 变量对象等价于this,等价于全局对象;
  • 函数执行环境的变量对象:
    • 函数执行环境的变量对象等价于活动对象;
    • 在初始化时添加arguments对象和形式参数;

全局环境的变量对象

全局对象的定义:

  • 全局对象是指在进入任何执行环境之前创建的对象;
  • 全局对象是唯一的;
  • 全局对象的属性可以在程序中的任何位置访问;
  • 全局对象的生命周期终止于程序执行结束;

全局对象创建时会用Math, String , Date, parseInt等属性进行初始化,也可以添加指向全局对象自身的属性,比如BOM中的window属性;

在引用全局对象的属性时,通常省略前缀,因为全局对象不能通过变量名引用;

其实可以通过全局执行环境中的this引用全局对象,或者BOM中的window属性递归的引用全局对象;

全局执行环境中,变量对象就是全局对象,所以能够对象的属性间接的访问全局变量,甚至在预先不知道变量名时动态的访问变量;

var a=new String("text");
alert(a);//直接访问变量对象;
alert(window.a);//由全局对象进行间接访问
var b="a";
alert(window[b]);//通过动态创建属性名间接访问

函数执行环境的变量对象

函数执行环境中变量对象不能直接访问,活动对象起到变量对象的作用;
活动对象在进入函数的执行环境是创建,其初始属性arguments引用参数对象;
arguments对象的属性:

  • callee: 引用当前函数;
  • length: 实际传入的参数的个数;
  • 数值属性:属性名为整型字符串,属性值为实际传入的参数值,按照参数列表中从左到右的顺序与数值属性对应;
    • 数值属性的值与形式参数的值是共享的, 对于没有实际传入的值不会更新;
    • 数值属性的个数与arguments.length值相等;
function foo( x, y, z ){
    alert(arguments.callee === foo); //true
    alert(foo.length); // 3, 形式参数的个数
    alert(arguments.length); // 2, 实际参数的个数
    //参数共享
    alert( x === arguments[0] );
    arguments[0] = 20;
    alert(x); //20
    x=30;
    alert(arguments[0]); // 30
    // arguments中只包含实际传入的参数
    z=40;
    alert(arguments[2]);// undefined
}
foo(10,20);

ES5中活动对象为lexical environment模型取代;

处理执行环境代码的两个阶段

执行环境代码的执行分为两个基本阶段:

  • 进入(entering)执行环境;
  • 代码执行;

这两个阶段是执行环境的一般行为,同时适用于全局执行环境和函数执行环境;

进入执行环境

进入执行环境时,在变量对象中创建以下属性:

  • 形式参数(函数执行环境):以形式参数的名值在变量对象上创建属性,对于没有传递值的参数,其属性值为undefined;
  • 函数声明:以函数的名和值创建属性,属性值引用函数对象;

如果变量对象中已经包含同名属性,原有的属性值将被覆盖;

  • 变量声明:以变量名创建同名属性,其值为undefined

如果变量对象中包含同名的形式参数或函数声明,变量声明不会对其产生影响;

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

进入执行环境阶段, 以上代码的变量对象如下所示:

  • test()函数的变量对象中不包含x函数,因为x是函数表达式;
  • _e也是函数表达式,因为赋值给给变量e,可以通过e进行访问,但e的值与其他变量声明一样为undefined
AO(test)={
    a: 10,
    b: undefined,
    c: undefined,
    d: <reference to functionDeclaration d>
    e: undefined;
}

代码执行

进入代码执行阶段时,变量对象中的属性已经创建,但是变量声明还未初始化,属性值为undefined;

在代码执行阶段会对变量对象中的变量声明进行赋值:

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

变量对象中仍然不包含函数x, 未赋值给其他变量的函数表达式的函数名只能在其定义内或者递归的调用;

以下代码的执行过程

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

进入执行环境:

VO={};
VO["x"]=<reference to functionDeclaration "x">;
VO["x"];// 不对变量对象产生影响

代码执行:

alert( x ); // function
VO["x"] = 10;
alert( x ); // 10
VO["x"] = 20;
alert( x ); / /20

即使代码不会执行,其中的变量或函数声明仍然在进入执行环境时添加到变量对象:

变量

一般来说在全局执行环境中使用 var 关键字可以声明全局变量,在其他位置不使用 var 关键字也可以声明全局变量,实际上这种说法是不对的;

变量只能通过 var 声明,不使用 var 关键字的赋值语句只是在全局对象上创建了新属性;

即认为不使用 var 关键字的赋值语句创建的不是变量,是因为这样创建的值不符合ECMAscript中变量的定义;

尽管var 声明的变量最终也成为了全局对象的属性,这是因为全局环境的变量对象等价于全局对象;

变量声明与赋值语句的区别:

  • 在进入执行环境阶段,变量声明在变量对象上添加属性;赋值语句在代码执行阶段才进行赋值;
alert(a);//undefined
alert(b);//not defined
b=10;
var a=20;
  • 声明过的变量具有{DontDelete}特性,不能使用delete操作符删除,而对象的属性可以删除;

在ES5中{DontDelete}特性改为[[Configurable]]特性,可以通过Object.definedProperty()方法修改;

//变量声明
var a = 20;
alert( window.a ); // 20
alert( delete a ); // false
alert (window.a ); // 20
//属性创建
b = 10;
alert( window.b ); // 10
alert(delete b); // true
alert(window.b); // undefined

eval()执行环境中变量的{DontDelete}属性没有定义;

eval("var a=10;");
alert(window.a);//10
alert(delete a);//true
alert(window.a);//undefined

__parent__属性

按照标准规范,变量对象是不能直接访问的,但在某些实现中通过的函数的__parent__属性可以引用其父级执行环境的变量对象;

从作用域的角度看,__parent__引用的是作用域链中的前一环;

posted @ 2016-09-22 11:49  青鹤  阅读(163)  评论(0)    收藏  举报