一步一步的理解闭包

     一步步的理解闭包:

  1. javascript是函数作用域,按常理来说,一个函数就不能访问另一个函数中的变量。
  2. 而我们实际操作有时候需要用一个函数去操作另一个函数中的变量。
  3. 为了能够访问另一个函数作用域中的变量,javascript提供一种内部机制,给嵌套在其它函数中的函数提供了一种父作用域链保存机制。
  4. 当内部函数创建时,静态的保存了父作用域链,即使父函数执行完毕,其变量对象(这里面保存着我们想要的父变量)一直保存在内部函数的作用域链中。内部函数不再被调用之前,这个活动对象一直在内存中(通俗讲就是这时候父函数的变量对象和内部函数绑定在一起了,同生共死)。这种机制取名为闭包。
  5. 简洁地说:闭包就是有权访问其他函数作用域变量的函数。

Figure 6. An execution context structure.

下文的叙述以此图为标准,若有疑问请参照

     下面我们来一些经典实例来进一步帮你解除闭包的困扰(js中最难的一个问题之一),嵌套函数由于其天然的嵌套形式是闭包最常见的一种表现方式。由嵌套函数开始吧 

1  function sayHello2(name){
2      var text='hello'+name;
3      var sayAlert=function(){alert(text);}
4      return sayAlert;
5  }
6  var say2=sayHello2('jack');
7  say2();//hello jack

     上面的代码就是一个嵌套函数,也是我们见过的最常见闭包表现形式,因为匿名函数function(){alert(text);}可以访问到外部函数sayHello2中的text变量,这就形成了闭包。闭包虽然存在了,但它也不能徒有其表,还的做点事情。代码第6行的赋值表达式后,外部函数sayHello2就被销毁了,按常理其内部变量text也随之销毁。可第7行成功的调用说明,外部函数的变量对象始终保留在内存中,直到内部函数销毁,不再被调用为止(总的有个头,不然内存扛不住)。

再看下面的例子

1 function say667(){
3     var num=666;
4     var sayAlert=function(){alert(num);}
5     num++;
6     return sayAlert;
7 }
8 var sayNum=say667();
9 sayNum();//667

     为什么最后是667,不是你期望的666?(有的地方是这样解释的,说是对父变量的引用,而不是复制。笔者认为基本类型不存在引用(地址)这一说吧)。保存着外部函数的活动对象,活动对象就是一个盒子。里面的变量,父函数可以随意改变。

     这个变量对象本来就是父函数的一部分,所以父函数能操作它是理所当然。而这个父变量对象也静态的保存在内部函数的作用域链中,那内部函数应该也有权利去操作它。

 1 function baz(){
 2     var x=1;
 3     return {
 4         foo:function foo(){return ++x;},
 5         bar:function bar(){return --x;}
 6             };
 7 }
 8 var closures=baz();
 9 alert(closures.foo(),//2
10     closures.bar()//1)

     你会发现内部的foo函数改变了父变量,与此同时也影响到了bar函数的结果。这是为什么?这是因为内部的两个函数在创建的时候都保存的是同一个父作用域—baz的作用域。这样一来父变量对象就是共享的了,所以里面的变量相互影响。(作用域包含变量对象,变量对象包含变量)

     再来看我们遇到过的一个循环问题了,同样是父函数一某种方式改变了这个变量对象中的变量值。使得内部函数最后访问的值变成了循环最终值。

 1 function list(){
 2     var result=[];
 3     for(var i=0;i<3;i++){
 4         result[i]=function(){
 5             alert(i);
 6         }
 7     }
 8 }
 9 result[0];//3
10 result[1];//3
11 result[2];//3
闭包的用途

下面看看闭包的用途吧,闭包可以用在许多地方。

  1. 基本功能就是前面提到的可以读取函数外部的变量,就不在赘述了。
  2. 另一个就是让外部函数变量的值始终保持在内存中(直到不存在这些变量的引用)。

     把你期望的值保存在一个地方,这与缓存的思想不谋而合。例如我们需要处理一个过程很耗时的函数对象,每次调用都会花费很长时间,那么我们需要把计算的值保存起来。调用的时候首先在缓存中查找,如果找不到则进行计算,然后更新缓存的值。如果找到了,直接返回找到的值即可。闭包正是可以做到这一点.

 1 var cacheSearchBox=(function(){
 2     var cache={}; 
4
return { 5 attachSearchBox:function(boxid){ 6 if(boxid in cache){ 7 return cache[boxid]; 8 } 9 var fsb=new searchBox(boxid); 10 cache[boxid]=fsb; 11 return fsb 12 } 13 } 14 })(); 15 cacheSearchBox.attachSearchBox("input1");

     这样,当我们第二次调用cacheSearchBox.attachSearchBox("input1")时,我们可以从缓存中获取该对象,而不用再去创建一个新的searchbox对象。

     3 面向对象中实现特权方法

     在下面的例子中,在person之外的地方无法访问其内部变量。只有通过闭包构造的特权方法的形式来访问。同时实现了私有变量和访问私有变量的特权方法的封装:

 1 var person=function(){
 2     var name="deng";
 3     return{
 4         getName:function(){
 5             return name;
 6         },
 7         setName:function(newName){
 8             name=newName;
 9         }
10     }
11 }();
12 alert(person.name);//undefined
13 alert(person.getName());//deng
14 person.setName("jack");
15 alert(person.getName());//jack
使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。实现面向对象封装中特权方法时,慎重修改私有变量。

文章在上纲上线的同时,加入了很多自己的理解。也许存在纰漏,望探讨。

posted @ 2014-06-13 18:05  明娃儿  阅读(886)  评论(0编辑  收藏  举报