zoukankan      html  css  js  c++  java
  • JS之闭包详细解读

    闭包在红宝书中的解释就是:有权访问另一个函数作用域中的变量的函数。

    1.变量作用域

    全局变量:所有的函数外部定义的变量,它的作用域是整个script。

    局部变量:定义在函数体内部的变量,作用域仅限于函数体内部。离开函数体就会无效。再调用就是出错。

    举例如下-局部变量:

    <script type="text/javascript">
        function fun(){
            var a = 100;
        }
        console.log(a);
    </script>

    a变量定义在fun函数内,是局部变量,所以它不能在外部被访问。

    举例如下-全局变量:

    <script type="text/javascript">
        var c = 100;
        function fun(){
            var a = 100;
            console.log(c)
        }
        fun();
        console.log(c);
    </script>

    在全局定义一个全局变量c,不仅能在fun函数内部被访问,在函数外依旧能被访问。

    2.间接访问局部变量

    <script type="text/javascript">
        function fun(){
            var a = 100;
            function fun1(){
                console.log(a);
            }
            fun1();
        }
        fun();
    </script>

    通过调用fun1把a打印出来,fun1是可以访问fun的所有变量

    3.作用域链

    可以参考我的这篇文章JS之预编译和执行顺序(全局和函数)可以更好的理解预编译的原理,为作用域链做准备。

    举例:

    <script type="text/javascript">
        var a = 100;
        function fun(){
            var b = 200
            function fun2(){
                var c = 300
            }
            function fun3(){
                var d = 400
            }
            fun2()
            fun3()
        }
        fun()
    </script>

    首先预编译,一开始生成一个GO{

      a:underfined

      fun:function fun(){//fun的函数体

          var b = 200
          function fun2(){
            var c = 300
          }
          function fun3(){
          var d = 400
          }
          fun2()
          fun3()
        }

    }

    逐行执行代码,GO{

      a:100

      fun:function fun(){//fun的函数体

          var b = 200
          function fun2(){
            var c = 300
          }
          function fun3(){
          var d = 400
          }
          fun2()
          fun3()
        }

    }

    当fun函数执行时,首先预编译会产生一个AO{

      b:underfined

      fun2:function fun2(){
           var c = 300
         }

      fun3:function fun3(){
          var d = 400
         }

    }

    这里注意的是fun函数是在全局的环境下产生的,所以自己身上挂载这一个GO,由于作用域链是栈式结构,先产生的先进去,最后出来,

    在这个例子的情况下,AO是后于GO产生的,所以对于fun函数本身来说,执行代码的时候,会先去自己本身的AO里找找看,如果没有找到要用的东西,就去父级查找,此题的父级是GO

    此刻fun的作用域链是  第0位    fun的AO{}

              第1位    GO{}

    fun函数开始逐行执行AO{

      b:200

      fun2:function fun2(){
           var c = 300
         }

      fun3:function fun3(){
          var d = 400
         }

     }

    注意:函数每次调用才会产生AO,每次产生的AO还都是不一样的

    然后遇到fun2函数的执行,预编译产生自己的AO{

      c:underfined

    }

    此刻fun2的作用域链是第0位    fun2的AO{}

              第1位    fun的AO{}

              第2位    GO{}

    然后遇到fun3函数的执行,预编译产生自己的AO{

      d:underfined

    }

    此刻fun3的作用域链是第0位    fun3的AO{}

              第1位    fun的AO{}

              第2位    GO{}

    fun2和fun3的作用域链没有什么联系

    当函数fun2和fun3执行完毕,自己将砍掉自己和自己的AO的联系,

    最后就是fun函数执行完毕,它也是砍掉自己和自己AO的联系。

    这就是一个我们平时看到不是闭包的函数。

    4.闭包

    1.闭包在红宝书中的解释就是:有权访问另一个函数作用域中的变量的函数。

    2.写法:

     1 <script type="text/javascript">
     2     function fun1(){
     3         var a = 100;
     4         function fun2(){
     5             a++;
     6             console.log(a);
     7         }
     8         return fun2;
     9     }
    10     
    11     var fun = fun1();
    12     fun()
    13     fun()
    14 </script>

    3.效果如下:

    4.分析:

    执行代码

    GO{

    fun:underfined

    fun1:function fun1()

       {

         var a = 100;

         function fun2()

        {

            a++;

            console.log(a);

         }

         return fun2;

         }

    }

    然后第十一行开始这里,就是fun1函数执行,然后把fun1的return赋值给fun,这里比较复杂,我们分开来看,

    这里fun1函数执行,产生AO{

    a:100

    fun2:function fun2(){

        a++;
        console.log(a);
        }

    }

    此刻fun1的作用域链为 第0位   AO

               第1位   GO

    此刻fun2的作用域链为 第0位   fun1的AO

               第1位   GO

    解释一下,fun2只是声明了,并没有产生调用,所以没有产生自己的AO,

    正常的,我们到第7行代码我们就结束了,但是这个时候来了一个return fun2,把fun2这个函数体抛给了全局变量fun,好了,fun1函数执行完毕,消除自己的AO,

    此刻fun2的作用域链为 第0位   fun1的AO

               第1位   GO

    第十二行就是fun执行,然后,它本身是没有a的,但是它可以用fun1的AO,然后加,然后打印,

    因为fun中的fun1的AO本来是应该在fun1销毁时,去掉,但是被抛给fun,所以现在fun1的AO没办法销毁,所以现在a变量相当于一个只能被fun访问的全局变量。

    所以第十三行再调用一次fun函数,a被打印的值为102.

    5.闭包之深入理解

    举例1:

     1 <script type="text/javascript">
     2     function fun1(){
     3         var a = 100;
     4         function fun2(){
     5             a ++;
     6             console.log(a);
     7         }
     8         
     9         return fun2;
    10     }
    11     var fn1 = fun1();   //生成自己的AO,上面有a
    12     var fn2 = fun1();
    13     fn1()//101
    14     fn1()//102
    15     fn2()//101
    16 </script>

    fn1和fn2互不干涉,因为fun1函数调用了两次,所以两次的AO是不一样的。

    举例2:

     1 <script type="text/javascript">
     2 function fun(){
     3     var num = 0;
     4     function jia(){
     5         num++;
     6         console.log(num);
     7     }
     8     function jian(){
     9         num--;
    10         console.log(num)
    11     }
    12     return [jia,jian];
    13 }
    14 var fn = fun();
    15 var jia = fn[0];
    16 var jian = fn[1];
    17 jia()//1
    18 jian()//0
    19 </script>

    jia和jian共用一个fun的AO,一动全都动,十二行返回了一个数组,

    举例3:

     1 <script type="text/javascript">
     2 function fun(){
     3     var num = 0;
     4     function jia(){
     5         num++;
     6         console.log(num);
     7     }
     8     function jian(){
     9         num--;
    10         console.log(num)
    11     }
    12     return [jia,jian];
    13 }
    14 var jia = fun()[0];
    15 var jian = fun()[1];
    16 jia()//1
    17 jian()//-1
    18 </script>

     这里有一个坑,jia = fun()[0]; jian = fun()[1];fun函数执行了两遍,所以两次的AO不一样,所以jia和jian操作的对象不一样。

    6.闭包好处与坏处

    好处:

    ①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

    ②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

    ③匿名自执行函数可以减少内存消耗

    坏处:

    ①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

    ②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

    7.闭包解决的问题

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
        <body>
            <ul>
                <li>0</li>
                <li>1</li>
                <li>2</li>
                <li>3</li>
                <li>4</li>
                <li>5</li>
                <li>6</li>
                <li>7</li>
                <li>8</li>
                <li>9</li>
            </ul>
            <script type="text/javascript">
                var lis = document.getElementsByTagName("li");
                for(var i = 0;i < lis.length;i++){
                    lis[i].onclick = function(){
                        console.log(i)
                    }
                    
                }
            </script>
        </body>
    </html>

    不管点击哪个都是10,那是因为点击事件是我们点击才触发的函数,等到触发的时候,i早就变成10跳出循环了,,这个时候我们就需要立即执行函数,创造了十个不同的作用域

    解决方案:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
        <body>
            <ul>
                <li>0</li>
                <li>1</li>
                <li>2</li>
                <li>3</li>
                <li>4</li>
                <li>5</li>
                <li>6</li>
                <li>7</li>
                <li>8</li>
                <li>9</li>
            </ul>
            <script type="text/javascript">
                var lis = document.getElementsByTagName("li");
                for(var i = 0;i < lis.length;i++){
    //                lis[i].onclick = function(){
    //                    console.log(i)
    //                }
                    (function(i){
                        lis[i].onclick = function(){
                            console.log(i)
                        }
                    })(i)
                }
            </script>
        </body>
    </html>
  • 相关阅读:
    laravel 服务容器,容器概念
    初识swoole
    一个小demo---递归计算子类下的某个值的总和
    微信支付の退款申请
    Box/Spout处理excel和csv
    mysql 获取指定日期的周/月开始 和 周/月结束
    时间字段规定模式获取
    异步服务器之心跳检测
    larave -- leftJoin IFNULL 链表查询
    Mac版Navicat破解
  • 原文地址:https://www.cnblogs.com/tangdiying/p/10136823.html
Copyright © 2011-2022 走看看