zoukankan      html  css  js  c++  java
  • javascript系列之变量对象

    引言

         一般在编程的时候,我们会定义函数和变量来成功的构造我们的系统。但是解析器该如何找到这些数据(函数,变量)呢?当我们引用需要的对象时,又发生了什么了?

         很多ECMAScript编程人员都知道变量和所处的执行上下文环境是密切相关的:

    1 var a=10;//全局上下文环境下的变量
    2 (function(){
    3     var b=20;//函数上下文环境下的局部变量
    4 })();
    5 alert(a);//10
    6 alert(b);//"b" 未定义

         当然,许多编程人员也知道。在当前规范版本下,隔离的作用域只能由“function”代码的执行上下文产生。与c/c++不同的是,例如ECMAScript中的for循环语句块不能产生局部的执行上下文:

    1 for(var k in {a:1,b:2}){
    2     alert(k);
    3 }
    4 alert(k);//即使循环结束,变量'k'任然在作用域中

         下面让我们看看,当我们声明我们的数据时发生的更多的细节。

    数据声明

         如果变量和执行上下文是密切联系的,就应该知道数据存储在哪里,如何获取这些数据。这种机制就称为变量对象。

    变量对象(VO)是一个与执行上下文和其存储位置密切联系的特殊对象:

    1. 变量(var ,变量声明);
    2. 函数声明(FD);
    3. 函数形参;

    在上下文中被声明。注意,在EC5中用词法环境模式取代了变量对象。

         理论上,可以把变量对象表示为一个常规的ECMAScript对象:VO={};正如我们所说,VO是执行上下文的一个属性:

    1 activeExecutionContext={
    2     Vo:{
    3         //上下文数据(var,FD,function arguments)
    4     }
    5 };

         一般不能直接引用变量。仅仅能(通过VO的属性名)引用全局上下文的变量对象(全局对象就是他自身的变量对象)。至于其他的执行上下文直接引用VO是不可能的,它仅仅是一种实现层面的纯粹机制。

         当我们声明一个变量或者函数时。我们除了构造VO的包含变量名称和变量值的属性,再没有其他东西了。比如:

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

        相应的变量对象是:

     1 //全局环境下的变量对象
     2 VO(globalContext)={
     3     a=10,
     4     test:<reerence to function>
     5 };
     6 //"test"函数上下文的变量对象
     7 VO(test functionContext)={
     8     x:30,
     9     b:20
    10 };

        但是在执行阶段(标准下),变量对象是一个抽象的本质。在具体的执行上下文中,VO的命名方式不同且有不同的初始结构。

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

        变量对象的一些操作(比如变量赋值)和行为在所有的执行上下文类型中都是相同的。从这一个角度看,把变量对象表示为一个抽象的基本概念是很方便的。函数上下文也可以定义一些与变量对象相关的附加信息。

    1 AbstratVO(变量对象实例化的一般过程)
    2 3       ╠══> GlobalContextVO
    4       ║        (VO === this === global)
    5 6       ╚══> FunctionContextVO
    7                (VO === AO, <arguments> object and <formal parameters> are added)

        下面让我们详细的来讨论下。

    全局上下文变量对象

         在这里,首先应该给出全局对象的定义:全局对象是在进入任何执行上下文前就已经构造出的一个对象;全局对象是唯一的(译者注:单例模式),在程序中的任何地方都可以获取它的属性,其生命周期随着程序的结束而结束。

         构造的全局对象被诸如Math,String,Date,parseInt等属性初始化。也可以通过一些可以引用全局对象自身的附加对象初始化。例如,在BOM中,全局对象的的window属性指向全局对象(然而,不是所有的实现都是这样的)

    1 global={
    2     Math:<...>,
    3     String:<...>,
    4     ....
    5     ....
    6     window:global
    7 };               

          当引用全局对象属性时,前缀通常是被省略的,因为全局对象不能直接通过名称获取。可能要通过全局上下文中的this值来获取,也可以通过递归引用它自身获取,例如BOM中的window,可以简写为:

    1 String(10);//表示global.String(10) ;
    2 //有前缀
    3 window.a=10;//===global.window.a=10===global.a=10;
    4 this.b=20;//global.b=20

         因此,回到全局上下文中的变量对象—这里的变量对象就是全局对象自身:VO(globalContex)===global;

         鉴于这些原因必须准确理解这个事实:在全局上下文声明的一个变量,我们可以通过全局对象的属性间接引用它(例如变量名是未知的)

    1 var a=new String('test')
    2 alert(a);//直接引用,在VO(globalCOntext):"test"
    3 alert(window['a']);//间接引用===VO(globalContext):"test"
    4 alert(a===this.a);//true
    5 var akey='a';
    6 alert(window[akey]);//间接引用,通过动态属性名:"test"
    函数上下文的变量对象

          对于函数执行上下文—VO是不能直接获取的,它的角色由活动对象(AO)扮演。VO(functionContext)===AO;当进入到一个函数上下文时,就产生了活动对象。并由值为Arguments对象的arguments属性初始化。

     1 AO={arguments:<Arguments Object> }

    Arguments对象是活动对象的属性。它包含了以下属性:

    • callee--函数自身的引用;
    • length--实参个数;
    • properties- indexes(整数,转化为字符),其值是函数参数的值(参数列表从左至右)。properties- indexes==arguments.length.也就是参数对象的properties-indexes值和当前(实际传入值)的形参是共享的
    1    function foo(x, y, z) {   
    3      // 已定义的函数参数 (x, y, z)个数
    4      alert(foo.length); // 3 
    6      // 实际传参数量(only x, y)
    7      alert(arguments.length); // 2
    9      // 函数自身的引用
    10      alert(arguments.callee === foo); // true
    12      // 参数共享
    14      alert(x === arguments[0]); // true
    15      alert(x); // 10
    17      arguments[0] = 20;
    18      alert(x); // 20
    20      x = 30;
    21      alert(arguments[0]); // 30
    23      // 然而对于未传参的z,arguments参数对象的索引属性时不共享的
    27      z = 40;
    28      alert(arguments[2]); // undefined
    30      arguments[2] = 50;
    31      alert(z); // 40
    33    }  

         在低版本的google浏览器中参数共享存在漏洞。在EC5中。活动对象的概念已经被词法环境的公有和单例模式所取代。

    处理上下文代码的阶段

         现在我们进入到文章的重点,处理执行上下文代码分为两个阶段:

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

         变量对象的修正与这两个阶段也是密切相关的。需要注意的是,这两个阶段的处理过程是一般性的行为并独立于上下文类型(也就是说,这个过程对于两种执行上下文-函数和全局都是平等的)

    进入执行上下文

        在进入执行上下文时(在代码执行执行前),VO已经被以下属性(他们已经在前文中提到)填充。

    • 对于函数的每一个形参(如果我们已经进入了函数执行上下文)--- 一个含有名称和形参值的变量对象属性就创建了,参数还未传值--也就是含有形参名和其值为undefined的属性被创建。
    • 对于每一个函数声明(FD)--- 一个含有函数对象名称和值的属性就创建了;如果变量对象已经包含了同名的属性,覆盖他的值和特性;
    • 对于每一个变量声明--- 一个含有变量名和其值为undefined的属性就创建了;如果这个变量名和已经声明的形参或函数名称一样,变量声明不能与已经存在的属性冲突(译者注:此变量名称不可用,换之)。

        让我们看下面的例子;

    1 function test(a,b){
    2     var c=10;
    3     function d(){};
    4     var e=function _e(){};
    5     (function x(){});
    6 }
    7 test(10)

       当进入含有实参10的test函数上下文时,AO如下:

    1    AO(test) = {
    2      a: 10,
    3      b: undefined,
    4      c: undefined,
    5      d: <reference to FunctionDeclaration "d">
    6      e: undefinedhttp://i.cnblogs.com/EditPosts.aspx?postid=3711963
    7    };

        注意,这个AO不包含函数X,这是因为X不是一个函数声明而是函数表达式(FE),表达式不影响VO。然而函数_e也是一个函数表达式,但我们将在VO里 面找到,这是因为把它赋值给变量e了,它是通过e来获取的。函数声明和函数表达式在后面会详细讨论。这些结束后就进入了处理上下文代码的第二个阶段--代 码执行阶段。

    代码执行

        在这个时候,AO/VO已经包含了这些属性(虽然不是所有的属性都有了我们传递的真实值,但大部分已经有了初始的值undefined).同样的例子,在代码解析时AO/VO做如下的修正: 

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

         我们还要注意的这个函数表达式_e仅仅只存在于内存中,因为保存在在已声明的变量e里。但是函数表达式x没有在AO/VO中,如果我们在定义之前或定义之 后调用x函数,我们将会得到错误:"x" is not defined.未保存的函数表达式仅能在它定义的地方调用或者递归的调用。

    一个经典实例:

    1 alert(x)//function x(){}
    2 var x=10;
    3 alert(x);//10
    4 x=20;
    5 function x(){}
    6 alert(x);//20

         为什么一开始弹出X是一个函数,且在声明之前就能过获取了?为什么不是10或者20?因为,根据规则—在进入上下文之前VO是被函数声明填充的。与此同 时,这里有一个变量声明x,但我们上面已经提到,语义化的变量声明阶段在函数声明和形参声明之后。在这期间变量还不能和已经声明的函数和形参名称冲突。因 此,在进入VO上下文时:

    1 VO={};
    2 VO['x']=<reference to FunctionDeclaration "x">
    3 //var x=10;
    4 //if function "x"还没定义,"x"为未定义。但是在这种情况下,变量声明不能干扰同名的函数。
    5 VO['x']=<值没有被破坏,任然是function>

    在代码执行阶段,VO修正如下

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

    我们在第二和第三个alert出的结果。

         在下面的例子中在进入上下文阶段我们再次看到变量放入了VO中(因此,else从不被执行,但尽管如此,变量b还是存在VO中):

    1 if(true){
    2     var a=1;
    3 }else{
    4     var b=1;
    5 }
    6 alert(a);//1
    7 alert(b);//undefined but not "b is not defined"
    关于变量

         许多关于javascript的文章甚至是书本说道:"使用var关键字(在全局执行环境)和不使用var关键字(在任何地方)声明全局变量是可能的"。其实不是这样的。请记住:变量只能通过var关键字声明。

         像这样赋值:a=10;仅仅创建了全局对象的新属性(而不是变量)。在这种意义下“Not the variable”并不是不能被改变的,但是在ECMAScript的变量概念下(由于VO(globalContext)===global,我们记住 了嘛?),它成为了全局对象的属性。

    不同之处在下面(通过例子来展示)

    1 alert(a);//undefined
    2 alert(b);//b is not defined
    3 b=10;
    4 var a=20;

        所有的都依赖于VO和他的修正阶段(进入执行上下文和代码执行阶段):

    进入上下文:

    1 VO = {a: undefined};

       我们看到在这个阶段这里没出现任何b,因为他不是变量。b仅仅在代码执行阶段出现(在这种情况下是不会有错的)。我们修改代码如下:

    1    alert(a); // undefined, we know why     
    3    b = 10;
    4    alert(b); // 10, created at code execution     
    6    var a = 20;
    7    alert(a); // 20, modified at code execution

        关于变量这里有更重要的一点。变量和简单的属性不同,有{DontDelete}属性,意味着不能通过delete操作符删除一个变量:

    1 a=10;
    2 alert(window.a);//10
    3 alert(delete a);//true
    4 alert(window.a);//undefined
    5 var b=20;
    6 alert(window.b);//20
    7 alert(delete b);//false
    8 alert(window.b);//still 20

         记住:在ES5中{DontDelete}重命名为[[Configureable]],并能通过Object.defineProperty方法手工管 理。然而有一种执行上下文中这个规则是不起作用的。他就是EVAL上下文:变量不再设置{DontDelete}属性:

    1    eval('var a = 10;');
    2    alert(window.a); // 10  
    4    alert(delete a); // true   
    6    alert(window.a); // undefined

        对那些在控制台来验证这些例子的调试工具来说,例如firebug:记住,firebug也是在控制台使用eval来执行你的代码。所以这些变量也没有{DontDelete}属性,并且可以被删除的。

    实现层的特征:_parent_属性

          我们已经注意到,在标准情况下。直接获取活动对象时不可能的。然而,在一些实现中,诸如SpiderMonkey 和 Rhino。函数有一个特殊的属性_parent_。他可以引用已经在函数中产生的活动对象。

    例子 (SpiderMonkey, Rhino):

    1 var global=this;
    2 var a=10;
    3 function foo(){}
    4 alert(foo._parent_);//global
    5 var VO=foo._parent_;
    6 alert(VO.a);//10
    7 alert(VO===global);//true

         以上的例子中我们看到函数foo()在全局上下文中构造,据此,他的_parent_属性设置为了全局上下文的变量对象也就是全局对象。然而在SpiderMonkey用同一种方式获取活动对象是不可能的:依据不同的版本,内部函数的_parent_返回null或者全局对象。

    在Rhino中,允许通过同样的方式获取活动对象:

     1 var global=this;
     2 var a=10;
     3 (function foo(){
     4     var y=20;
     5     //"foo"函数上下文的活动对象
     6     var AO=(function(){})._parent_;
     7 alert(AO.y);//20
     8 //当前活动对象的_parent_已经变成了全局对象。这样变量对象的一个特殊的链就形成了,就是所谓的作用域链
     9 alert(AO._parent_===global);//true
    10 alert(AO._parent_.x);//10
    11 })()
    总结

          在这篇文章中我们继续深入的与执行上下文有关的对象。我希望这些材料是有用的而且讲清楚了某些你以前觉得的方面。以后的计划,在下面的章节中将会讲到作用域链,确定标示符,最终是闭包。

  • 相关阅读:
    Windows 网络监测ping IP输出时间
    python
    遇见问题汇总
    在路上积累
    Condition
    ReentrantReadWriteLock
    AbstractQueuedSynchronizer
    jmeter使用
    使用VisualVM监控java进程
    CNVD漏洞证书(2)
  • 原文地址:https://www.cnblogs.com/mingwaer/p/3718128.html
Copyright © 2011-2022 走看看