• 深入理解javascript闭包(一)


    闭包(closure)是Javascript语言的一个难点。也是它的特色,非常多高级应用都要依靠闭包实现。

    一、什么是闭包?

    官方”的解释是:闭包是一个拥有很多变量和绑定了这些变量的环境的表达式(一般是一个函数),因而这些变量也是该表达式的一部分。


    相信非常少有人能直接看懂这句话,由于他描写叙述的太学术。事实上这句话通俗的来说就是:JavaScript中全部的function都是一个闭包。只是一般来说,嵌套的function所产生的闭包更为强大。也是大部分时候我们所谓的“闭包”。

    要理解闭包,首先必须理解Javascript特殊的变量作用域。


    二、变量的作用域

    变量的作用域无非就是两种:全局变量和局部变量。

    Javascript语言的特殊之处。就在于函数内部能够直接读取全局变量。


    var n=999;
    
    function f1(){
        alert(n);
    }
    
    f1(); // 999

    还有一方面,在函数外部自然无法读取函数内的局部变量。

    function f1(){
      var n=999;
    }
    
    alert(n); // error

    这里有一个地方须要注意,函数内部声明变量的时候。一定要使用var命令。假设不用的话,你实际上声明了一个全局变量!

    function f1(){
       n=999;
    }
    
    f1();
    
    alert(n); // 999

    三、怎样从外部读取局部变量?

    出于种种原因,我们有时候须要得到函数内的局部变量。可是,前面已经说过了,正常情况下,这是办不到的,仅仅有通过变通方法才干实现。

    那就是在函数的内部,再定义一个函数。

    function f1(){
        n=999;
        function f2(){
          alert(n); // 999
        }
    }

    在上面的代码中,函数f2就被包含在函数f1内部,这时f1内部的全部局部变量。对f2都是可见的。可是反过来就不行。f2内部的局部变量,对f1 就是不可见的。

    这就是Javascript语言特有的“链式作用域”结构(chain scope),

    子对象会一级一级地向上寻找全部父对象的变量。所以,父对象的全部变量。对子对象都是可见的,反之则不成立。

    既然f2能够读取f1中的局部变量,那么仅仅要把f2作为返回值。我们不就能够在f1外部读取它的内部变量了吗。

    function f1(){
    
        n=999;
    
        function f2(){
          alert(n);
        }
    
        return f2;
    
    }
    
    var result=f1();
    
    result(); // 999

    上面代码中的f2函数,就是闭包。



    各种专业文献上的“闭包”(closure)定义非常抽象,非常难看懂。我的理解是。闭包就是可以读取其它函数内部变量的函数。

    因为在Javascript语言中。仅仅有函数内部的子函数才干读取局部变量,因此能够把闭包简单理解成“定义在一个函数内部的函数”。

    所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。


    四、闭包的用途

    闭包能够用在很多地方。它的最大用处有两个,个是前面提到的能够读取函数内部的变量。还有一个就是让这些变量的值始终保持在内存中

    怎么来理解这句话呢?请看以下的代码。

    function f1(){
    
        var n=999;
    
        nAdd=function(){n+=1}
    
        function f2(){
          alert(n);
        }
    
        return f2;
    
    }
    var result=f1();
    result(); // 999
    nAdd();
    result(); // 1000

    在这段代码中,result实际上就是闭包f2函数。它一共执行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中。并没有在f1调用后被自己主动清除。

    为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中。而f2的存在依赖于f1,因此f1也始终在内存中。不会在调用结束后,被垃圾回收机制(garbage collection)回收。

    这段代码中还有一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用varkeyword。因此 nAdd是一个全局变量,而不是局部变量。

    其次。nAdd的值是一个匿名函数(anonymous function)。而这个

    匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,能够在函数外部对函数内部的局部变量进行操作。


    五、使用闭包的注意点

    1)因为闭包会使得函数中的变量都被保存在内存中。内存消耗非常大,所以不能滥用闭包。否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量所有删除。

    2)闭包会在父函数外部。改变父函数内部变量的值。

    所以,假设你把父函数当作对象(object)使用。把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便

    改变父函数内部变量的值。



    六、思考题

    假设你能理解以下代码的执行结果。应该就算理解闭包的执行机制了。

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

    七、JavaScript闭包样例

    function outerFun()
     {
      var a=0;
      function innerFun()
      {
       a++;
       alert(a);
      }    
     }
    innerFun()
    
    //上面的代码是错误的.innerFun()的作用域在outerFun()内//部,所在outerFun()外部调用它是错误的.
    
    //改成例如以下,也就是闭包:
    
    function outerFun()
    {
     var a=0;
     function innerFun()
     {
      a++;
      alert(a);
     }
     return innerFun;  //注意这里
    }
    var obj=outerFun();
    obj();  //结果为1
    obj();  //结果为2
    var obj2=outerFun();
    obj2();  //结果为1
    obj2();  //结果为2

    再次来看什么是闭包?

    当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,假设内部函数引用了位于外部函数的变量,当外部函数调用完成后,这些变量在内存不会被 释放,由于闭包须要它们。

    再来看一个样例

    function outerFun()
    {
     var a =0;
     alert(a);  
    }
    var a=4;
    outerFun();
    alert(a);

    结果是 0 , 4 .  由于在函数内部使用了varkeyword 维护a的作用域在outFun()内部.

    再看以下的代码:

    unction outerFun()
    {
     //没有var
     a =0;
     alert(a);  
    }
    var a=4;
    outerFun();
    alert(a);

    结果为 0 , 0 真是奇怪,为什么呢?

    作用域链是描写叙述一种路径的术语,沿着该路径能够确定变量的值 .当运行a=0时,由于没有使用varkeyword,因此赋值操作会沿着作用域链到var a=4;  并改变其值.


    假设你对javascript闭包还不是非常理解,能够再看下篇文章



  • 相关阅读:
    .NET Framework 4 不能先解压再使用setup.exe安装的解决方法
    PNG透明度兼容IE6的方法
    VM虚拟机访问宿主机本地站点
    AU3设置非全局快捷键的函数GUISetAccelerators
    Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
    关于Android Studio gradle build running很久的问题
    不懂积累,你怎么成长
    关于ScrollView里的显示不完问题
    关于DialogFragment里控件无法赋新值问题
    Android上传Base64图片,图片变成黑色一块的问题
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5286188.html
Copyright © 2020-2023  润新知