我眼中的闭包

    想起来几年前就开始接触闭包这个名词了,但是一直对其概念不是很理解,网上有很多对闭包讲解的,虽看过很多但都没真正搞懂,

下面是我个人对闭包的一些理解……

  很多人对闭包的理解就是:两个函数相互嵌套,当内部函数访问外部函数定义的一个变量,然后返回内部函数到全局中就形成一个闭包。就像我下面所讲的第一个例子。

  或许这正是很多人第一次接触闭包概念时,就形成的印象。 当然闭包并不只是这样,其实从技术上将,任何函数都是一个闭包,因为它都能访问其外部定义的变量。而且闭包的形成并不是因为其内部访问了外部函数定义的变量,即使没有访问外部函数的变量也是闭包,因为其实函数在创建时其内部的[[scope]]始终引用了外部的作用域链

  先看一个最常见的闭包例子

function father(){
   var age=45;
   return function son(){
      return age;
   } 
}
var sonFun=father();//调用父函数,返回子函数son的引用给sonFun变量
var vAge=sonFun();//调用函数sonFun,返回45

 

   上面定义一个父函数father,然后里面嵌入了一个类似子函数son, 当执行函数father时其实就是将内部的子函数的引用返回出来,然后存在一个全局变量sonFun里,

然后再次执行函数sonFun,返回结果45存到vAge变量。

Ok,问题来了,当father函数执行完毕按理来说age已经被销毁,为什么son还能访问到它呢?

   要解答上面的问题就涉及到作用域的问题了:

   首先来看father函数

   当father函数被创建的时候, 其内部的[[scope]]属性指向该函数被创建时的作用域,这个作用域其实就是一个链表scopeChain1,只不过此时的链表中只有一个window对象。

当father被调用时,会创建一个Variable Object可变对象(函数中也可以称活动对象Activation Object)activation1,然后创建一新的作用域链scopeChain2,初始化时链表只有一项指向father函数[[scope]]链表中的window对象,然后将刚才创建的activation1对象的引用插入到链表的顶端,也就是此时链表有两个对象activation1,windpw。

接下来就是为刚才创建的activation1设置各种属性。如添加属性age,创建son函数并设置为activation1中的属性,设置age属性为45,如果有参数还会设置参数属性,然后设置arguments属性等。

    上面说到了创建函数son:具体为在创建函数son时又会对其内部的[[scope]] 属性设置为其创建时所在的作用域,在也就是执行father函数时的作用域链scopeChain2(注意该

scopeChain2中的activation1有一个age属性),当然此时此时只是创建函数son2并没用执行该函数中的任何逻辑操作。创建完成后返回son函数的引用赋值给外部的全局变量sonFun。 

因为sonFun是全局中的,浏览器关闭之前不会被销毁,所以内部被创建的函数被保存了下来。

     接下来执行sonFun函数 又开始创建一个激活对象activation2,然后再创建新的一个作用域链scopeChain3,初始化链表时指向其创建时所在环境的作用域中的那些对象,即scopeChain2中的activation1和window,然后再将activation2添加到链表顶端,这样scopeChain3链表中就有三项,从顶端往下分别是activation2,activation1,window。然后设置activation2的各个属性,这里貌似没有其他属性可以设置,所以只能初始化一个arguments。接下来访问age,然后就开始在作用域链scopeChain3中寻找,从上往下找。activation2 中没有,activation1中找到了age然后返回值得拷贝给vAge变量。OK所有过程结束,大致过程就是这样。

   下面然看一个 实际中的例子,相信这个例子各个地方都随处可见。

<html>
<head>
<script type="text/javascript">
window.onload
=function(){
   
var btns=document.getElementsByName("button");
   
for(var i=0;i<btns.length;i++){
      btns[i].onclick
=function(){
        alert(i);
      }
   }
}
</script>
</head>
<body>
<input type="button" id="btn1" name="button" value="按钮1"/>
<input type="button" id="btn2" name="button" value="按钮1"/>
<input type="button" id="btn3" name="button" value="按钮1"/>
</body>
</html>

    发现无论点击哪一个按钮都是弹出3,很怪异!!!

    如果你看懂我上面所讲的那个例子,相信这里你也应该明白了,如果还没明白,没事,来分析下。

全局变量中定义一个变量i初始化为0。

    这边我们找到3个按钮,然后依次对其onclick属性指定一个方法,来处理相应的click事件。

    首先循环到i=0的时候,然后找到按钮1,创建一个函数,创建的过程会将其内部的[[scope]]属性设置为函数所在作用域,在这里是全局 所以也就是[[scope]]所指向的作用域中只有一个winodw对象,然后返回一个函数引用地址给按钮1的onclick属性,这样其实该函数对象就被保存下来了。很简单这里做的就只是一个创建函数,至于其内部的什么alert(i)根本没执行,此时没什么用处。

    然后进入下轮循环i=1,找到按钮2,又创建一个新的函数 ,创建过程同上面的逻辑一模一样,设置[[scope]]属性,该属性指向的作用域链中仍旧只有window。也是返回函数的引用地址给按钮2的onclick属性,这样 此时创建的函数也被保存下来了。

   接着又进入下轮循环i=2,找到按钮3,仍旧创建一个新的函数,接下来的还是一样,就不罗嗦了。

   最后执行完i++,退出循环此时i=3;

   接下来当点击按钮1时开始执行绑定的click函数, 执行的过程就是创建一个activation1对象,创建一个作用域链scopeChain1上例子已经讲过,scopeChain1 初始化时只有一项,即指向函数[[scope]]中的window对象,然后将activation1的引用添加到scopeChain1的顶端,接着当执行alert(i)时,就会尝试去找i,从链的顶端往下找,先从activation1中找,再到window中找,在window中找到i,i此时为3。同样其他按钮的执行也是如此,所以其实他们所访问的i都是window.i,当然都是3了。

   那么如何改变使其输出不一样呢,这就是闭包要做的事了。下面改下

   

btns[i].onclick=function(i){
                      return function(){
                        alert(i);
                      }
              }(i)

 这样每次点击时就会依次弹出0,1,2了。

 这样其实相当于在返回的函数在执行时其作用域链中多了一层外部函数创建的激活对象,即此时作用域链中依次为内部函数激活对象,外部函数激活对象,window对象。而此时访问的i其实是外部函数激活对象中的i而不是window中的i。这里的外部函数激活对象是通过将全局变量i的值拷贝一份到实参中,然后设置激活对象的属性i为该实参的拷贝值。这个激活对象的i和全局变量中的i没有任何关系只是名字重名而已,形参中的i可以改成任何其他名称如num之类的。

最后再贴两个例子……如果你理解了下面两个例子,估计闭包也就理解的差不多了。

//例1
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
alert(object.getNameFunc()());
//例2
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
            var that=this;
      return function(){
        return that.name;
      };
    }
  };
alert(object.getNameFunc()());

第一个例子执行完 object.getNameFunc() 返回函数function (){return this.name;}所以再次执行时已经是在全局环境下了,访问的this为window,所以输出“The Window”

第二个例子执行完 object.getNameFunc() 返回函数function (){return that.name;}虽然也是在全局环境下了,但是因为that变量存在于父函数的执行时所产生的激活对象中,而给that赋值的this在执行时代表的是object对象,所以输出为"My Object"

 

 

 

posted @ 2011-12-23 14:52  自由小菜园  阅读(175)  评论(0)    收藏  举报