浅谈对闭包的理解

一、什么是闭包


官方解释:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。咋一看,这句话晦涩难懂,一脸懵逼。直接上代码,便于理解
function test( ){                     
          var a = 0;
          return function( ){ 
               a++;
               alert(a);
      } 
}
var atest = new test( ); //引用返回的函数
atest( );  // 1
atest( ); //  2

 上面的代码有两个主要的地方: 

  1、函数嵌套函数; 

  2、函数test返回内部匿名函数

根据执行结果可以发现,变量a一直保存在内存中,所有才会每调用一次atest(),返回的结果会自加1;究其原因,当执行到var atest = new test()时,函数test开始执行,完事之后,准备释放一波内存,结果返现其中返回的匿名函数应用了变量a,所以test()并不会出栈。想要更加透彻的理解这个过程,需要对js函数的作用域链比较熟悉。


二、闭包的原理

如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。 

  1、当定义函数test的时候,js解释器会将函数test的作用域链(scopechain)设置为定义test时test所在的“环境”,如果test是一个全局函数,则scope chain中只有window对象。 

  2、当函数test执行的时候,test会进入相应的执行环境(excutioncontext)。 

  3、在创建执行环境的过程中,首先会为test添加一个scope属性,即test的作用域,其值就为第1步中的scope chain。即test.scope=test的作用域链。 

  4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到test的作用域链的最顶端。此时test的作用域链包含了两个对象:test的活动对象和window对象。 

  5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数test时所传递的参数。 

  6、最后把所有函数test的形参和内部的函数(暂且命名为b)的引用也添加到test的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即test的作用域。 

 到此,整个函数test从定义到执行的步骤就完成了。此时test返回函数b的引用给atest,又函数b的作用域链包含了对函数test的活动对象的引用,也就是说b可以访问到test中定义的所有变量和函数。函数b被atest引用,函数b又依赖函数test,因此函数test在返回后不会被GC回收。 

  当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、test的活动对象和window对象,如下图所示:

 

  如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数test的活动对象,依次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

三、闭包的应用

看一个题目:针对下面这个ul,编写js实现点击每一列的时候alert其index

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script>
       window.onload =  function init(){
            var lis=document.querySelectorAll("#text li");
            for(var i=0;i<lis.length;i++){
                lis[i].οnclick=function(){
                    alert(i);
                }
            }
        }
    </script>
</head>
<body>
<ul id="text">
    <li>这是第一个li</li>
    <li>这是第二个li</li>
    <li>这是第三个li</li>
</ul>
</body>
</html>

现在要求点击每个li输出的是对应的序号而不是全是3.

1、首先不使用闭包来做:

  
       window.onload = function init(){
            var lis=document.querySelectorAll("#text li");
            for(var i=0;i<lis.length;i++){
                lis[i].οnclick=function(event){
                    //获取事件源对象
                    var src=event.srcElement||event.target();
                    //遍历lis数组
                    for(var n=0;n<lis.length;n++){
                        //判断数组中那个元素等于src
                        if(lis[n]==src){
                            //输入当前下标
                            alert(n);
                        }
                    }
                }
            }
        }
2、使用闭包来实现:

        
    window.onload = function init(){
            var lis=document.querySelectorAll("#text li");
            for(var i=0;i<lis.length;i++){
                lis[i].οnclick=(function(e){
                    return function(){
                        alert(e);
                    };
                })(i)
            }
        }
实现思路
1.首先在遍历数组进行绑定onclick事件的时候,把当前的i传入事件函数中,并保存下来了。
2.把换行去掉后,在看看当前绑定的函数lis .οnclick=(function(e){return function(){alert(i);};})(i)
3.当点击对应li元素的时候,事件函数返回一个function(){alert(i);}这样的方法。
4.但因为外层的function是一个自调函数,所以实际上应该是这样的(function(){alert(i);})(i)
5.所以输出的结果就是对应li数组中的下标。









  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值