以下根据汤姆大叔深入javascript系列文章整理整理。如果想深入了解,请阅读汤姆大叔的系列文章。http://www.cnblogs.com/TomXu/...变量对象初步介绍变量对象(简称VO)是与执行上下文相关的特殊对象,它存储了上下文中声明的如下内容:变量(var,变量声明);函数声明(FunctionDeclaration,缩写为FD);函数参数我们可以使用普通的ECMAScript对象来表示一个变量对象:VO={};VO是执行上下文的属性(property),所以:activeExecutionContext={VO:{//上下文数据(var,FD,functionarguments)}};只有全局上下文的变量对象允许通过VO的属性名间接访问(因为在全局上下文中,全局对象本身就是一个变量对象),在其他上下文中,不能直接访问VO对象,因为它只是内部机制的实施。全局上下文中的变量对象只有全局上下文中的变量对象才允许通过VO的属性名间接访问在全局上下文中,有VO(globalContext)===global;因为我们在全局上下文中声明的变量在变量对象中都是全局的,而全局上下文中的全局变量对象就是全局对象本身。所以我们可以通过VO的属性名间接访问vara=newString('test');警报(一);//直接访问,发现在VO(globalContext):"test"alert(window['a']);//通过全局间接访问:global===VO(globalContext):"test"alert(a===this.a);//truevaraKey='a';alert(window[aKey]);//通过动态属性名间接访问:“test”函数上下文中的变量对象不能被函数执行上下文中的VO直接访问。这时候,激活对象(简称AO)就起到了VO的作用。VO(functionContext)===AO;在理解函数上下文中的变量对象时,我们通过两个阶段处理上下文代码来理解1.进入执行上下文2.当执行代码进入执行上下文并进入上述文本的执行时,也就是之前代码执行完毕,此时VO包含以下属性函数参数函数声明变量声明其中,函数声明级别最高,然后是函数参数,最后是变量声明。较高级别的声明可以覆盖较低级别的声明。在代码执行周期中,AO/VO已经有了属性(但是并不是所有的属性都有值,大部分属性的值还是系统默认的初始值undefined)。这时候就会进行赋值操作和代码执行。警报(x);//函数varx=10;alert(x);//10x=20;函数x(){};警报(x);//20进入上下文阶段,因为函数级别最高,所以第一个alert(x)输出的是一个函数。之后进行变量赋值,分别alert10和20。功能栏(x){警报(x);varx=2;}bar(3);//3由于形参声明比变量声明高一级,alert(3),因为变量在进入执行上下文时不能被重写形参声明,所以输出的是3而不是undefined。不使用var就声明全局变量是错误的。警报(一);//undefinedalert(b);//"b"没有声明,报错b=10;变量a=20;scopechain函数上下文的作用域链是在调用函数时创建的,包括活动对象和这个函数内部的[[scope]]属性。函数上下文包括以下内容:activeExecutionContext={VO:{...},//orAOthis:thisValue,Scope:[//范围链//所有变量对象的列表//用于标识符查找]};其作用域定义如下:Scope=AO+[[Scope]][[scope]]是所有父变量对象的层次链,??在当前函数上下文之上,函数创建时存储在其中。请注意这一点——[[scope]]在创建函数时被存储——静态地(不可变),永远永远,直到函数被销毁。即:函数永远不能被调用,但是[[scope]]属性被写入并存储在函数对象中。另一件需要考虑的事情——与作用域链相反,[[scope]]是函数的属性,而不是上下文。因此,我个人的理解是作用域链应该是函数本身的活动对象+父类的变量对象。其中,函数本身的活动对象永远排在第一位。在查找标识符时,如果在当前活动对象中找不到,则会遍历作用域链上的父变量对象。其中,[[scope]]在函数创建时存储,与函数共存。变量x=10;functionfoo(){alert(x);}(function(){varx=20;foo();//10,但不是20})();函数的作用域链在函数创建时定义,是静态的,调用时不会改变。更深入理解闭包作用域链varfirstClosure;varsecondClosure;functionfoo(){varx=1;firstClosure=function(){返回++x;};secondClosure=function(){return--x;};x=2;//影响两个闭包共有的[[Scope]]中的AO["x"],alert(firstClosure());//3,通过第一个闭包的[[Scope]]}foo();alert(firstClosure());//4alert(secondClosure());//3firstClosure和secondClosure函数创建时,内部变量x是从父函数foo的变量对象x中引用的,所以实际上两个函数共享一个作用域,所以x变量是共享的。经典闭包vardata=[];for(vark=0;k<3;k++){data[k]=function(){alert(k);};}数据[0]();//3,而不是0data[1]();//3,而不是1data[2]();//3,而不是2解释同上。函数创建时,通过访问作用域链得到内部变量k,即父变量对象k。调用函数时,for循环已经执行完毕。此时K为3,所以调用三个函数时,输出值为3。vardata=[];对于(vark=0;k<3;k++){data[k]=(function_helper(x){returnfunction(){alert(x);};})(k);//传入"k"值}//现在结果正确data[0]();//0数据[1]();//1数据[2]();//2创建一个匿名函数,通过传入k变量作为参数,函数执行时,由于内部形参可以访问k变量,所以不需要在父作用域链上查找,所以最终输出达到预期目的。这里解释了闭包的理论定义。开发人员经常错误地将闭包理解为从父上下文返回内部函数,甚至理解只有匿名函数才能成为闭包。在ECMAScript中,闭包指的是:1.从理论上讲:所有的函数。因为它们在创建的时候都保存了上层上下文的数据。即使对于简单的全局变量也是如此,因为在函数中访问全局变量等同于访问自由变量,此时使用的是最外层作用域。2、从实用的角度来看:以下函数被认为是闭包:1、即使创建它的上下文已经被破坏,它仍然存在(例如,内部函数从父函数返回)2、自由代码中引用了变量
