一步一步的理解闭包
一步步的理解闭包:
- javascript是函数作用域,按常理来说,一个函数就不能访问另一个函数中的变量。
- 而我们实际操作有时候需要用一个函数去操作另一个函数中的变量。
- 为了能够访问另一个函数作用域中的变量,javascript提供一种内部机制,给嵌套在其它函数中的函数提供了一种父作用域链保存机制。
- 当内部函数创建时,静态的保存了父作用域链,即使父函数执行完毕,其变量对象(这里面保存着我们想要的父变量)一直保存在内部函数的作用域链中。内部函数不再被调用之前,这个活动对象一直在内存中(通俗讲就是这时候父函数的变量对象和内部函数绑定在一起了,同生共死)。这种机制取名为闭包。
- 简洁地说:闭包就是有权访问其他函数作用域变量的函数。
下文的叙述以此图为标准,若有疑问请参照
下面我们来一些经典实例来进一步帮你解除闭包的困扰(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 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)闭包会在父函数外部,改变父函数内部变量的值。实现面向对象封装中特权方法时,慎重修改私有变量。
文章在上纲上线的同时,加入了很多自己的理解。也许存在纰漏,望探讨。