闭包

作者: huanghaodong | 来源:发表于2018-11-27 14:33 被阅读0次

    什么是闭包

    “高三”上解释——闭包是指有权访问另一个函数作用域中变量的函数。
    通俗的将,若一个函数的执行需要用到其他的函数内变量,那么这个函数就闭包。如:

    function a(){
            var i = 1;
            function b(){   //b就是一个闭包,只是我们无法在函数a外部调用这个闭包。
                console.log(i);
            }
        }
    

    闭包可以在外部环境中被调用

    function a(){
            var i = 1;
            return function b(){
                    console.log(i);
                }
        }
    
    var test = a();//函数a返回匿名函数,即闭包,给变量test
    
    test();//执行闭包
    

    闭包的作用

    主要作用用两个,一是调用其它函数中的局部变量,二是将其它函数中的局部变量保存在内存中。

    <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Document</title>
            <script>
            function f1(){
        var n=999;
        nAdd=function(){n+=1}//nAdd是一个全局变量,实现在任何时候任何地方都可以控制f1函数内部的n
        function f2(){
          alert(n);
        }
        return f2;
      }
      var result=f1();
      result(); // 999
      nAdd();
      result(); // 1000
    //一共调用了两次闭包,第一次弹出999,第二次弹出1000,说明了局部变量n一直保存在内存中
            </script>
        </head>
        <body>
            
        </body>
        </html> 
    

    小心闭包!

    1.闭包有一个特性,就是只能保存外部环境变量的最后一个值。我们通过一个典型的例子来说明:
    我们尝试通过for循环来为一个空数组arr赋值

        var arr = [];
        for(var i = 0;i < 5;i++){
            arr[i] = function(){
                return i;
            }
        }
        //测试arr数组
        console.log(arr[0]());//5
        console.log(arr[1]());//5
        console.log(arr[2]());//5
        console.log(arr[3]());//5
        console.log(arr[4]());//5
    
    控制台的输出结果让我很是吃惊,尼玛弄撒呢?不是应该是0,1,2,3,4吗?咋全是5!
    正如开头所说闭包只能保存外部环境变量的最后一个值,外部环境变量i循环结束过后最终值为5,所以闭包中所有的i都为5。好的,讲完了。。。呵呵,这里肯定还是会有许多顽强的小玩伴会问:为啥?
    现在我们来深入理解一下上面这个函数的运行机制。上面函数每一次循环都会将function(){return i;}这个匿名函数直接赋值给每一个数组项。然后通过arr[0]()调用执行匿名函数,此时匿名函数需要访问变量i,所以匿名函数会在外部作用域中寻找i,根据闭包的定义,无疑这个匿名函数永远都是一个闭包,闭包只能保存外部环境变量的最后一个值,此时的i很明显等于5,所以arr[0]()会返回5。另外三个数值项也是这个道理。
    问题的原因找到了,那怎么才能得到我们想要的0,1,2,3,4呢?每一个数组项的匿名函数function(){return i;}返回值都是通过在外部作用域中找到的,但是等匿名函数开始寻找时,外部的i早已成了一个定值5。我是否可以在外部作用域和匿名函数之间再添加一个作用域(函数),匿名函数从这个新增作用域中获取所需变量。新增函数通过立即执行的形式,从外部环境实时获取参数,相当于在新增函数作用域内定义了一个变量,这个变量就是我们的匿名函数执行时所需要的变量!
    
        var arr = [];
        for(var i = 0;i < 5;i++){
            (function(m){
                arr[m] = function(){
                    return m;
                }
            })(i);
        }
        //测试arr数组
        console.log(arr[0]());//0
        console.log(arr[1]());//1
        console.log(arr[2]());//2
        console.log(arr[3]());//3
        console.log(arr[4]());//4
    
    拿arr[0]来说,arr[0]的值为function(){return m;},现在执行它,arr[0]()需要在外部作用域中寻找变量m,果然在它的包含函数中找到了m,因为这个包含函数是一个立即执行的函数表达式(IIFA),它在for循环时就已经实时地传入了实参i并赋值给形参m,所以arr[0]()就会返回我们想要的结果。这里的IIFA有两个效果:1、封装一个函数作用域。2、立即执行函数以传递实参(如果函数不执行时不会传递参数的)。
    IIFE也可以这样写:
    
    for(var i = 0;i < 5;i++){
      arr[i] = (function(m){    //arr[i]中的i具有实时性,所以arr[i]在IIFA内里面外面都行。问题的关键是我们必须将闭包包含在IIFA中,然后闭包向IIFA索取变量。
        return function(){
          return m;
        }
      })(i);        
    }
    

    疑惑差不多就解决了吧。还有一个开发中常碰到的类似问题,批量注册事件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <style type="text/css">
        li{
            list-style: none;
            float: left;
            width:200px;
            height:200px;
            border:1px solid red;
        }
        </style>
    </head>
    <body>
        <ul>
            <li class="li1"></li>
            <li class="li2"></li>
            <li class="li3"></li>
            <li class="li4"></li>
            <li class="li5"></li>
        </ul>
        <script type="text/javascript">
            //获取元素
            var lis = document.getElementsByTagName("li");
            //批量注册事件
            for(var i = 0;i<lis.length;i++){
                //iife 解决批量添加监听的时候出现的问题 尽量避免在事件函数中调用外部的变量,特别是循环变量,因为闭包。
                //解决办法一
                //写法一
                /*(function(m){lis[m].onclick = function(){
                  console.log("我是老"+(m+1));
                  }
                })(i)*/
                
                //写法二
                lis[i].onclick = (function(m){
                  return function(){
                    console.log("我是老"+(m+1));
                  }
                })(i)
    
                //解决办法二
                //给每个li强行添加属性 让这个li记住i  
                // lis[i].o=i;
                // lis[i].onclick = function(){
                //      //输出this 
                // alert("我是老"+(this.o+1))
                // }
            }
        </script>
    </body>
    </html>
    

    2.因为闭包可以保存其它函数的局部变量,所以闭包比普通函数更占用内存。解决办法,在闭包调用结束后将闭包调用函数赋值为null。

    相关文章

      网友评论

          本文标题:闭包

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