频道栏目
首页 > 资讯 > JavaScript > 正文

浅谈对闭包的理解

16-12-17        来源:[db:作者]  
收藏   我要投稿

一、什么是闭包

官方解释:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

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

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

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

<!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].onclick=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].onclick=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].onclick=(function(e){  
                return function(){  
                    alert(e);  
                };  
            })(i)  
        }  
    }

实现思路

1.首先在遍历数组进行绑定onclick事件的时候,把当前的i传入事件函数中,并保存下来了。

2.把换行去掉后,在看看当前绑定的函数lis.onclick=(function(e){return function(){alert(i);};})(i)

3.当点击对应li元素的时候,事件函数返回一个function(){alert(i);}这样的方法。

4.但因为外层的function是一个自调函数,所以实际上应该是这样的(function(){alert(i);})(i)

5.所以输出的结果就是对应li数组中的下标。

相关TAG标签
上一篇:无限长数字的加减运算
下一篇:JS之模拟QQ列表展示
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站