zoukankan      html  css  js  c++  java
  • 【转】再说一说闭包

    原文:http://www.tuicool.com/articles/ruyiQ3E

    初涉闭包

    闭包是指有权访问另一个函数作用域变量的 函数 ,创建闭包的通常方式,是在一个函数内部创建另一个函数

    上文我们提到了,由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包, 

    闭包的本质还是函数 , 闭包的本质还是函数 闭包的本质还是函数 。

    function A(){
            var x = 1;
            return function(){
                x++;
                console.log(x);
            }
        }
        var m1 = A();
    

      

    上面就是一个很简单的闭包例子,通过m函数,我们可以获得A函数内部变量的值,这个样例比较简单,看不出什么问题,接下来我们来深入了解一下。

    -------------------------------从简单到复杂的分割线,请做好准备----------------------------------------------------

    4. 闭包详解

    难点一:判断作用域指向的变量对象是否相同

     function A(){
            var x = 1;
            return function(){
                x++;
                console.log(x);
            }
        }
        var m1 = A();//第一次执行A函数
        m1();//2
        m1();//3
        var m2 = A();//第二次执行A函数
        m2();//2
        m1();//4
    

      

    上面这个例子其实可以引出几个问题:

    1.为什么连续执行m1的时候,x的值在递增?

    2.定义函数m2的时候,为什么x的值重新从1开始了?

    3.运行m2以后,为什么再运行m1,x还是按照之前m1的运行结果继续增长?(其实就是m1和m2里面的x为什么是相互独立,各自维持的?)

    额,这图画的可能真的有点丑),不要慌,图上虽然画的有点乱,但是其实很简单:左半部分和上面简单闭包的例子,其实是完全一样的,而右边半部分,与左边其实是完全对称的;注意看图上的重点: 每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留 ,所以,最终剩下两个A的变量对象, 因此m1和m2在操作x时,指向的是不同的数据 ,

    function A(){
            var x = 1;
            var m=[];
            m[0] = function(){
                x++;
                console.log(x);
            };
            m[1] = function(){
                x++;
                console.log(x);
            }
             return m;
        }
        var m = A();//第一次运行A,而且只运行这一次
        m[0]();//2
        m[1]();//3
        m[0]();//4
        m[1]();//5
    

      其是还要配上灵魂画师级别的配图

    --------------------------------------------------------------------------------------------------------------

    详解js中的闭包

    前言

    在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以《javascript高级程序设计》里面的理论为基础。用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正。

    写在闭包之前

    闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,所以有必要先弄清楚以下几个概念:

    1. 执行环境和活动对象

    - 执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的 变量对象(variable object) ,执行环境中定义的变量和函数就保存在这个变量对象中;

    全局执行环境是最外围的一个执行环境,通常被认为是window对象

    执行环境和变量对象在 运行函数 时生成

    执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)

    2. 作用域链

    当代码在一个执行环境中执行时,会创建 变量对象 的一个 作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的 访问顺序 ;

    作用域链的最前端,始终是 当前代码执行环境的变量对象 ,如果这个环境是函数,则其活动对象就是变量对象

    作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境

    在函数执行过程,根据当前执行环境的作用域链来 逐层向外 查找变量,并且进行标识符解析

    是不是觉得以上的理论很枯燥而且艰涩?因为基本上是从书上引用来的,不着急着理解,先摆在上面,等会结合案例回头再来看!接下来请看样例:

    样例1
    <script>
        var a = 2;
        function A(){
             var a = 1;
        }
        console.log(A());//1
    </script>

    以这段简单的代码为例,根据上面的理论画一下关系图(直接用ps画的,原谅我拙劣的笔迹):

    如图所示,在执行函数A的时候,创建了A的执行环境和变量对象,其中A的变量对象和全局变量对象中都含有a变量,根据作用域链从前向后查找,在A的变量对象中找到,所以输出1,执行完毕以后 , A的指向环境销毁,A的变量对象由于没有被引用,所以也销毁 ;

    样例2
    <script>
        function A(){
             var a = 1;
        }
        console.log(a);// 报错  a is not defined
    </script>

    这个例子比较简单,要画图的话只需要画一个全局变量对即可,因为在js中,外围环境无法访问内围局部变量(其实本质就是作用域链上找不到相应的值),所以这里会报变量未定义的错误。

    样例3
    <script>
          function A(){
             var a = 1;
             function B(){
                 if(a==1){
                     console.log(1)
                 }
                 else
                 {
                      console.log(0);
                  }
             }
             B();
        }
        A();//1
    </script>

    上面这个例子,在函数A中定义了函数B,关系图如下:从图上可以很清楚的看出,在每个执行环境中可以访问到的变量对象,所以B可以访问A的变量对象和全局变量对象中的变量以及自身变量对象,A可以访问自身变量对象和全局变量对象

    关于执行环境和作用域链暂时说到这里,下面进入正题,讲闭包;

    3. 初涉闭包

    闭包是指有权访问另一个函数作用域变量的 函数 ,创建闭包的通常方式,是在一个函数内部创建另一个函数

    上文我们提到了,由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包, 闭包的本质还是函数 , 闭包的本质还是函数 闭包的本质还是函数 。

    样例4
    <script>
        function A(){
            var x = 1;
            return function(){
                console.log(x);
            }
        }
        var m = A();
        m();//1
    </script>

    上面就是一个很简单的闭包例子,通过m函数,我们可以获得A函数内部变量的值,这个样例比较简单,看不出什么问题,接下来我们来深入了解一下。

    -------------------------------从简单到复杂的分割线,请做好准备----------------------------------------------------

    4. 闭包详解

    难点一:判断作用域指向的变量对象是否相同

    样例5
    <script>
        function A(){
            var x = 1;
            return function(){
                x++;
                console.log(x);
            }
        }
        var m1 = A();//第一次执行A函数
        m1();//2
        m1();//3
        var m2 = A();//第二次执行A函数
        m2();//2
        m1();//4
    </script>

    上面这个例子其实可以引出几个问题:

    1.为什么连续执行m1的时候,x的值在递增?

    2.定义函数m2的时候,为什么x的值重新从1开始了?

    3.运行m2以后,为什么再运行m1,x还是按照之前m1的运行结果继续增长?(其实就是m1和m2里面的x为什么是相互独立,各自维持的?)

    其实要解决上面的问题,我们就要用到前面铺垫的知识点了:

    首先,先画一下结构图,

    (额,这图画的可能真的有点丑),不要慌,图上虽然画的有点乱,但是其实很简单:左半部分和上面简单闭包的例子,其实是完全一样的,而右边半部分,与左边其实是完全对称的;注意看图上的重点: 每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留 ,所以,最终剩下两个A的变量对象, 因此m1和m2在操作x时,指向的是不同的数据 ,

    现在来回答上面的三个问题:

    1.(为什么连续执行m1的时候,x的值在递增?)

    answer:因为m1在引用的活动对象A一直没有释放(想释放的话可以让m1=null),所以x的值一直递增。

    2.定义函数m2的时候,为什么x的值重新从1开始了?

    answer:因为又一次运行了A函数,生成一个新的A的活动对象,所以m2的作用域链引用的是一个新的x值。

    3.m1和m2里面的x为什么是相互独立,各自维持的?

    answer:因为在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,所以,m1和m2的作用域链是指向不同的A的活动对象的。

    好的,到这里先回顾一下前面说到的知识点:

    执行环境和变量对象在 运行函数 时生成

    执行环境中的所有代码执行完以后, 执行环境 被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)

    感觉理解了吗?接下来,再看看另一个很类似的例子:

    样例6
     <script>
        function A(){
            var x = 1;
            var m=[];
            m[0] = function(){
                x++;
                console.log(x);
            };
            m[1] = function(){
                x++;
                console.log(x);
            }
             return m;
        }
        var m = A();//第一次运行A,而且只运行这一次
        m[0]();//2
        m[1]();//3
        m[0]();//4
        m[1]();//5
    </script>

    这个例子和刚刚十分类似,不同的是,在A内部就先定义了两个函数,可以看出 ,最后的结果与上面的例子有些不同:

    变量x仍然能保持递增,但是m[0]和m[1]定义的函数,对于x的改变不再是相互独立的,其实大家估计猜到了,这里的m[0]和m[1]的作用域指向的A的变量对象,其实是同一个,为什么呢?很简单,看看刚刚这段代码,其实是只调用了一次A函数,再看上文那句话:

    执行环境和变量对象在 运行函数 时生成

    既然A只执行一次,那么A的活动变量当然也就生成了一个,所以这里m[0]和m[1]的作用域指向同一个A的变量对象

    难点二:判断变量对象中变量的值

    样例7
     <script>
        function A(){
            var funs=[];
            for(var i=0;i<10;i++){
               funs[i]=function(){
                   return i;
               }
            }
            return funs; 
        }
        var funs = A();//定义funs[0]-funs[9],10个函数
        console.log(funs[0]());//10
        console.log(funs[1]());//10
        console.log(funs[6]());//10
    </script>

    这个例子其实算是一个经典案例,在很多地方都有提到,执行完毕后 funs数组中,funs[0]-funs[9]存的其实都是一样的,都是一个返回i值的函数, 这个例子容易错误的地方其实在于,弄错了产生执行环境的时机 ,还是看这句话:

    执行环境和变量对象在 运行函数 时生成

    所以,当执行 var funs = A(); 时,只是 定义 函数,而没有 执行 ,真正产生环境变量的时间是在 console.log(funs[0]()); 这三句的时候,此时A的变量对象中i值是什么呢?很简单, 看它return的时候 ,i的值,显然,i的值是10,所以,最后三句输出的都是10

    好的,针对以上的案例,如果我就是想让fun[i]能够返回i,那应该怎么写呢?在《javascript高级程序设计》中,提供了一种参考的写法:

    样例8
     <script>
        function A(){
            var funs=[];
            for(var i=0;i<10;i++){
               funs[i] = function anonymous1(num){
                            return function anonymous2(){
                        return num;
                    }
                }(i);
            }
            return funs; 
        }
        var funs = A();//定义funs[0]-funs[9],10个函数
        console.log(funs[0]());//0
        console.log(funs[1]());//1
        console.log(funs[6]());//6
    </script>

    是不是一看头就大了?没关系,接下来我们慢慢分析,当然,上述代码中anonymous1和anonymous2两个名字是我自己添加上的,为了后面能够更好的说明。

    首先,先来看看function anonymous1(num){}(i),这是一个 立即执行函数 ,效果和名字一样,定义完之后马上运行结果, 那这里运行的结果是什么呢?就是把i的值立即传递给num这个局部变量 ,然后再返回anonymous2, 请注意这个立即执行函数被执行的次数,10次 ,再来看看这句话

    执行环境和变量对象在 运行函数 时生成

    好的,那现在请回答我:

    这里面生成了几个anonymous1的活动变量?

    answer:当然也是10个,

    那每个anonymous1活动变量中存贮的num值是多少?

    answer:看anonymous函数return的时候可以知道,存贮的num值就是每次传入的i值,也就是0-9

    好了,那现在很明了了,这样的写法其实相当于, 把每次的i值都保存在一个anonymous1活动变量钟,给最内层的anonymous2函数使用

    小结

    写到这里,关于闭包的主要特征和辨别方式已经基本讲到了,个人感觉因为这个问题比较抽象,还是多看看文中以及网上的一些例子,加深理解。以上内容属于个人见解,如果有不同意见,欢迎指出和探讨。希望能对看到的人有所帮助,同时,码字不易(尤其是还要配上灵魂画师级别的配图~),请尊重作者的版权,转载请注明出处,如作商用,请与作者联系,感谢!

  • 相关阅读:
    【SCOI 2011】 糖果
    【POJ 3159】 Candies
    【POJ 1716】 Integer Intervals
    【POJ 2983】 Is the information reliable?
    【POJ 1364】 King
    【POJ 1201】 Intervals
    【POJ 1804】 Brainman
    6月10日省中提高组题解
    【POJ 3352】 Road Construction
    【POJ 1144】 Network
  • 原文地址:https://www.cnblogs.com/oxspirt/p/6031103.html
Copyright © 2011-2022 走看看