美文网首页
闭包之我的理解

闭包之我的理解

作者: Mescal川 | 来源:发表于2016-10-26 23:31 被阅读19次

    闭包是javascript的一个难点,也是一个重点,很多高级应用都需要用到闭包。在学习闭包的过程中看了不少书和大牛的博客,下面来说说我对闭包的理解和看法。

    函数

    要说闭包就不得不先说一下函数,这个javascript的中流砥柱。
    众所周知在ES6之前js都没有class这个关键字,一切的面向对象都是通过function模拟的。

    一个简单到不能再简单的例子:函数内部可以访问外部的变量(全局变量)。

    var n=100;
    function f1()
    {
        console.log(n);
    }
    f1();//100
    

    而外部却不能访问函数内的局部变量。

    function f1()
    {
        var n=100;
    }
    console.log(n);//n is not defined
    

    那么问题来了,如果我想访问函数内的局部变量怎么办?一种解决办法就是使用闭包。
    什么是闭包?我个人认为函数中的函数就是闭包,有点像java的内部类,既然函数可以访问外部变量,那么函数中的函数也可以访问外层函数的变量,这个内层函数就是闭包。

    function f1()
    {
        var n=100;
        function f2()
        {
            console.log(n);
        }
        f2();
    }
    f1();//100
    

    上述写法比较麻烦,因此可以把f2作为一个返回值返回给f1。

    function f1()
    {
        var n=100;
        function f2()
        {
            console.log(n);
        }
        return f2;
    }
    f1()();//100
    //相当于var f=f1();f();
    

    下面来看一个问题:这里有5个li,名字是0到4,我希望我点击0的时候在控制台输出0,点击1的时候在控制台输出1,以此类推。

    var lis=document.getElementsByTagName("li");
    for(var i=0;i<lis.length;i++)
    {
        lis[i].onclick=function()
        {
            console.log(i);
        }
    }
    

    奇怪的事情发生了,无论我点击哪一个在控制台输出的都是5。
    这是为什么呢?
    仔细观察,我为每个li创建了一个匿名函数,匿名函数形成了闭包,它们都在引用外部的i,当i改变时,自然所有匿名函数里面的i都改变了。用几张图来说明。




    当i增加到5的时候不再满足循环条件,由于五个匿名函数都是引用同一个i,所以它们打印出来的都是5。

    既然它们是引用同一个i,那么只需为每一个i创建一个备份:

    var lis=document.getElementsByTagName("li");
    for(var i=0;i<lis.length;i++)
    {
        lis[i].onclick=function(num)
        {
            return function()
            {
                console.log(num);
            }
        }(i)
    }
    

    这里在匿名函数接收一个参数并且立即执行,匿名函数内部又创建了一个函数负责把这个参数返回给匿名函数,由于是按值传递的,所以相当于给i做了一个备份。就算i一直在改变,而每个匿名函数的num是固定的。

    闭包中的this

    在闭包中使用this会出现一些问题,在全局函数中this指向的是window,当函数以方法被调用时,this就指向这个对象。但是,匿名函数的执行环境具有全局性,this一般指向window。参考红宝石书中的一个经典的问题:

    var name="The Window";
    var object={
        name:"My Object",
        getNameFunc:function(){
            return function(){
               return this.name;
            };
        }
    };
    alert(object.getNameFunc()());//"The Window"
    

    内部函数在搜索this的时候只会搜到其活动对象为止,由于匿名函数的执行环境具有全局性,因此当前的活动对象就是window。

    可以通过改变当前的活动对象来改变this的指向:

    var name="The Window";
    var object={
        name:"My Object",
        getNameFunc:function(){
            var that=this;
            return function(){
                return that.name;
            };
        }
    };
    alert(object.getNameFunc()());//"My Object"
    

    这里把this对象赋值给了一个that变量,因此当前的活动对象就从window变成了object。

    闭包引发的问题

    闭包虽好,但是也引发了一系列问题,尤其是内存泄漏。js的垃圾回收机制是引用计数的,当一个变量不再使用的时候(引用计数为0)垃圾回收机制就会把它清理掉。而闭包的存在会导致引用数至少为1。

    function f()
    {
       var myDiv=document.getElementById("Div1");
       myDiv.onclick=function()
       {
            console.log(myDiv.id);
       };
    }
    

    上述代码中,函数f需要等myDiv清除后才能被清除,而myDiv由于匿名函数的存在,它的引用数至少为1,因此它所占用的内存永远不会被回收。

    解决办法:

    function f()
    {
       var myDiv=document.getElementById("Div1");
       var id=myDiv.id
       myDiv.onclick=function()
       {
            console.log(id);
       };
       myDiv=null;
    }
    

    把myDiv.id保存在一个变量中,并且在闭包中引用这个变量,这样就与myDiv没有关系了。但是闭包会引用包含函数的整个活动对象,因此必须把myDiv设为null来解除对它的引用,确保垃圾回收机制能够把资源回收。

    使用闭包应该注意的问题:闭包虽然好,但是在实际应用中尽量减少 闭包的使用,并且为了防止内存泄漏,要时刻记得在闭包完后清除它的局部变量。

    相关文章

      网友评论

          本文标题:闭包之我的理解

          本文链接:https://www.haomeiwen.com/subject/fcejuttx.html