• 深入理解javascript中的闭包!(转)


    1、闭包的经典错误

    假如页面上有若干个div,我们想给它每个绑定一个onclick方法,于是有了下面的代码。

    function A(){
        var divs=document.getElementsByTagName("div");
        for(var i=0; i<divs.length; i++){
             divs[i].onclick=function(){
                 alert(i);
        }
      }
    }

    我原以为每次都弹出相对应的i,可是结果却是每次都弹出divs.length的值。于是查资料后知道解决方法是这样的。

    function A(){
       var divs=document.getElementsByTagName("div");
       for(var i=0; i<divs.length; i++){
          (function(n){
            divs[i].onclick=function(){
              alert(n);
         }
        })(i)
     }
    }
    

     2、内部函数

    因为是刚开始学习js,所以就从基础知识说起,什么是内部函数呢?说白了,内部函数就是定义在另一个函数中的函数,例如:

    function Fn(){
       function subFn(){
       };//subFn就是一个内部函数,它定义在Fn函数里面。
    }

    由于subFn被包含在Fn里,这就意味着在Fn内部调用subFn函数是有效的,而在Fn外部调用subFn则是无效的。下面的代码会导致一个javascript的错误:

    function Fn(){
       document.write("Fn");
       function subFn(){
          document.write("subFn");
      }
    }
    subFn();

    不过在Fn内部调用subFn函数则是有效地:

    function Fn(){
       document.write("Fn");
       function subFn(){
          document.write("subFn");
      }
       subFn();
    }
    Fn();

     2.1、伟大的逃脱

    既然目前这样我们不能够在Fn函数外部调用subFn,那怎么才能实现让Fn函数外部也能调用subFn呢?javascript运行开发人员像传递任何类型的数据一样传递函数(这是关键),也就是说,javascript中的内部函数可以逃脱定义他们的外部函数。

    (1)逃脱的方法有很多中:比如可以将一个内部函数指定给一个全局变量:

    function Fn(){
      document.write("Fn");
      function subFn(){
         document.write("subFn");
      }
      global=subFn;
    }
    Fn();
    global();

    当在调用Fn()函数时,会修改全局变量global,这时候它的引用为subFn;此后调用global和subFn是一样的效果。如果在Fn外部直接调用subFn仍然是会出错的,因为内部函数虽然通过引用保存在全局变量中实现了逃脱,但这个函数的名字依然只存在于Fn的作用域中。

    (2)也可以通过父函数的返回值来获得内部函数的引用。

    function Fn(){
       document.write("Fn");
       function subFn(){
          document.write("subFn");
     }
       return subFn;
    }
    var subRef=Fn();
    subRef();
    

    这里并没有在Fn的内部修改全局变量,而是返回了一个对subFn的引用,通过调用Fn这个函数可以获取subFn的引用,而且这个引用可以保存在变量中。

    这种即使离开函数作用域的情况下仍能够调用内部函数的事实,意味着只要存在调用内部函数的可能,javascript就要保留被引用的函数。而且javascript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,javascript垃圾收集器才能释放相应的内存空间。

    说了半天终于说道和闭包的关系了,闭包是指有权访问另一个函数作用域的变量的函数,创建闭包的常见方式则是在一个函数内部创建另一个函数,就是我们上述说的内部函数。所以刚才说的不是废话,也是和闭包有关的。

    1.2、变量的作用域

    内部函数也可以有自己的变量,这些变量都被限制在内部函数的作用域中。

    function Fn(){
       document.write("Fn");
       function subFn(){
          var subVar=0;
          subVar++;
          document.write("subFn	");
          document.write("subVar="+subVar+"<br/>");
      }
      return subFn;
    }
    var subRef=Fn();
    subRef();
    subRef();
    var subRef2=Fn();
    subRef2();
    subRef2();

    每当通过引用和其他方式调用这个内部函数时,就会创建一个新的subVar变量,然后加1,得出的结果如下:

    Fn
    subFn    innerVar = 1
    subFn    innerVar = 1
    Fn
    subFn innerVar = 1 subFn innerVar = 1

    内部函数也可以像其他函数一样引用全局变量。

    var global=0;
    function Fn(){
      document.write("Fn");
      function subFn(){
        global++;
        document.write("subFn	");
        document.write("globalVar="+global+"<br/>");
     }
      return subFn();
    }
    var subRef=Fn();
    subRef();
    subRef();
    var subRef2=Fn();
    subRef2();
    subRef2();

    现在每次调用这个内部函数都会持续的递增这个全局变量的值。

    Fn
    subFn    globalVar = 1
    subFn    globalVar = 2
    Fn
    subFn    globalVar = 3
    subFn    globalVar = 4
    
    

    如果这个变量是父函数的局部变量呢?因为内部函数会引用到父函数的作用域,内部函数也可以引用到这些变量。

    function Fn() {
         var outerVar = 0;
         document.write("Fn<br/>");
         function subFn() {
            outerVar++;
            document.write("subFn	");
               document.write("outerVar = " + outerVar + "<br/>");
                }
            return subFn;
       }
      var subRef = Fn();
      subRef ();
      subRef ();
      var subRef2 = outerFn();
      subRef2();
      subRef2();
    

     这次的结果非常有意思,也许会出乎我们的意料。

    Fn
    subFn    outerVar = 1
    subFn    outerVar = 2
    Fn
    subFn    outerVar = 1
    subFn    outerVar = 2
    

    我们看到的是前两种情况合成的效果,通过每个引用调用的subFn都会独立递增的outerVar,也就是说第二次调用Fn并没有继续沿用outerVar的值,而是在第二次调用的作用域创建并绑定了一个新的outerVar实例,两个计数器无关。

    当内部函数在定义他的作用域外部被引用时,就创建了该内部函数的一个闭包,这种情况下我们称既不是内部函数局部变量,也不是其参数的变量为自由变量,称外部函数的调用坏境为封闭闭包的坏境,从本质上讲,如果内部函数引用了外部函数的变量,相当于授权了该变量能够被延迟使用,因此当外部函数被调用完后,这些变量的内存不会被释放(最后的值会被保存),闭包仍然需要使用它们。

    3、解惑

    现在回过头来看刚才那个经典错误。就会明白为什么每次都弹出divs.length;

    function A(){
        var divs=document.getElementsByTagName("div");
        for(var i=0; i<divs.length; i++){
             divs[i].onclick=function(){
                 alert(i);
        }
      }
    }
    

    上面的代码在页面加载后就会执行,当i的值为divs.length时判断条件不成立,for循环执行完毕,但是因为每个divs的每个onclick方法为内部函数,所以i被闭包引用,内存不能销毁,i的值一直保持divs.length,直到程序改变它或者所有的onclick函数被销毁(主动把函数设为null或者页面卸载)时才会被回收,这样我每点击一次div,onclick函数就会查找i的值,一查等于divs.length,所以就每次都弹出的是divs .length;而第二种方法是使用了一个立即执行的函数又创建了一个闭包,函数声明方法括号内就成了表达式,后面在加上括号,括号就是调用咯,这是把i当参数传入,函数立即执行,num保存每次i的值。

    这样,大家对闭包有一定的了解了吧。当然完全了解的话需要把函数的执行坏境和作用域链搞清楚。我个人觉得还蛮详细的。辛苦了~~~

  • 相关阅读:
    SqlServer存储过程
    SQL Server :DBLINK创建及使用
    jQuery控制TR的显示隐藏
    JAVA------22.git导出项目,启动项目
    JAVA------21.String转时间,并向前推进一天
    JAVA------20.经纬度转换省市区
    JAVA------20.图片中插文字和图片
    JAVA------19.生成二维码并插入logo
    JAVA------18.excel导出
    JAVA------17.charles(青花瓷)抓包工具,适用安卓
  • 原文地址:https://www.cnblogs.com/ILYljhl/p/3301234.html
Copyright © 2020-2023  润新知