最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 我这个学渣也看懂的闭包

    正文概述 掘金(嘻嘻嘻嘻嘻嘻666)   2021-03-23   407

    文指出处segmentfault.com/a/119000000…

    参考文章segmentfault.com/a/119000001…

    要清楚闭包 首先要清楚几个概念 执行环境 , 活动对象, 作用域链

    执行环境(Execution context,EC)和活动对象

    执行环境也叫执行上下文

    执行环境分为三种(全局执行环境window,函数执行环境,evel()执行环境)

    js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中

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

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

    作用域链

    当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数访问顺序; 作用域链的最前端,始终是当前代码执行环境的变量对象,如果这个环境是函数,则其活动对象就是变量对象 作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境 在函数执行过程,根据当前执行环境的作用域链来逐层向外查找变量,并且进行标识符解析

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

    我这个学渣也看懂的闭包

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

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

    我这个学渣也看懂的闭包

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

    样例2

    <script>
        function A(){
             var a = 1;
             return a ;
        }
        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>  
    
    

    我这个学渣也看懂的闭包

    从图上可以很清楚的看出,在每个执行环境中可以访问到的变量对象,所以B可以访问A的变量对象和全局变量对象中的变量以及自身变量对象,A可以访问自身变量对象和全局变量对象

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

    初涉闭包

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

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

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

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

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

    闭包详解

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

    样例5

        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(funs0);这三句的时候,此时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函数使用


    起源地下载网 » 我这个学渣也看懂的闭包

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元