一步一步的理解闭包

     一步步的理解闭包:javascript

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

Figure 6. An execution context structure.

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

     下面咱们来一些经典实例来进一步帮你解除闭包的困扰(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?(有的地方是这样解释的,说是对父变量的引用,而不是复制。笔者认为基本类型不存在引用(地址)这一说吧)。保存着外部函数的活动对象,活动对象就是一个盒子。里面的变量,父函数能够随意改变。性能

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

 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的做用域。这样一来父变量对象就是共享的了,因此里面的变量相互影响。(做用域包含变量对象,变量对象包含变量)code

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

 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
闭包的用途

下面看看闭包的用途吧,闭包能够用在许多地方。blog

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

文章在上纲上线的同时,加入了不少本身的理解。也许存在纰漏,望探讨。