zoukankan      html  css  js  c++  java
  • JavaScript闭包浅谈

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

    作者:willingtolove;

    本文链接:http://www.cnblogs.com/willingtolove/p/4745889.html

    1. 变量的作用域:

      在javascript中,局部变量的作用域是由它定义的函数决定的,嵌套函数可以访问它的外部作用域的变量。

      EX1:

    1 function hello() {
    2     var name = "world";
    3     function hi() {
    4         alert(name);
    5     }
    6     hi();
    7 }
    8 hello();

       上述代码的运行结果:弹出框   “world”;

       因为name这个变量的作用域是1-7行(hello这个函数内);

       要想详细理解请查阅:作用域与作用域链;这个对理解闭包很有用!

     2. js的垃圾回收机制

       js具有自动垃圾收集机制,开发人员不用再关心内存使用问题;这种垃圾收集机制原理很简单:找出不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按固定的时间间隔

    (或按代码设定的)周期性的执行这一操作。

       函数中局部变量的生命周期:局部变量只在函数执行的过程中存在。这个过程中,会为变量在栈或堆上分配相应的空间,以便存储它们的值。当函数结束后,该变量就没用了,就会被垃圾

    收集器所标记,等待回收。下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是有时候,这个函数内部又嵌套了另一个函数,而这个内部函数使用了外部函数的变量,并且

    内部函数在外面被调用.这时垃圾回收机制就会有问题.如果在外部函数结束后,又直接调用了其内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定

    义的时候,会自动把函数和它可能使用的变量(包括本函数内变量和父级和祖先级函数的变量)一起存储起来,相当于存储了一个环境,也就是构建一个闭包,这些变量(或者说是环境)将不会被内

    存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了...),才会销毁这个闭包,而没有任何一个闭包引用的变量会被下一次内存回收启动时所回收.

     3. 闭包的定义:

        闭包是涉及独立变量的的函数;换句话说,在闭包中定义的函数会记住它创建的环境;

        也有这样理解的:闭包就是能够读取其他函数内部变量的函数;由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。

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

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

    其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。

        创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量;

        EX2:

    1 function hello() {
    2     var name = "world";
    3     function hi() {
    4         alert(name);
    5     }
    6     return hi;
    7 }
    8 var myHello = hello();
    9 myHello();

         上面代码的运行结果与EX1中的效果一样;

         为什么呢?

         这段代码有两个特点:

        1、函数hi嵌套在函数hello内部;

        2、函数hello返回函数hi。  

         这样在执行完var myHello=hello()后,变量myHello实际上是指向了函数hi,再执行myHello()后就会弹出一个窗口显示变量name的值。这段代码其实就创建了一个闭包,为什么?

    因为函数hello外的变量myHello引用了函数hello内的函数hi,就是说:当函数hello的内部函数hi被函数hello外的一个变量引用的时候,就创建了一个闭包。

         代码执行第8行时,name变量生命周期开始,但当代码执行到第9行时,name变量的生命周期并没有结束;

       通过上面的例子,可以这样理解闭包:当一个函数在它所创建的环境之外执行时,就是闭包(函数myhello就是一个闭包)

       闭包是一个特殊的对象,它包括两部分:① 函数;②创建函数的环境;

         EX3:   闭包例子

        

     1 function play() {
     2     var num = 1;
     3     return function () {
     4         alert(num++)
     5     };
     6 }
     7 var myPlay = play();
     8 myPlay();// 结果:1 
     9 myPlay();// 结果:2
    10 myPlay = null;//num被回收!
    11 myPlay();//错误: 缺少对象

        分析:内部函数在返回前,会将所有已访问过的外部函数中的变量(num)在内存中锁定,也就是说,这些变量将常驻 myPlay的内存中,不会被垃圾回收器回收;

    num被锁定在myPlay的闭包里,那么每次执行 myPlay的时候,num都会自增一次;

        EX4:  闭包例子

     1 function Add(x) {
     2     return function (y) {
     3         return x + y;
     4     };
     5 }
     6 
     7 var Add5 = Add(5);
     8 var Add10 = Add(10);
     9 
    10 console.log(Add5(2));
    11 console.log(Add10(2));

         运行结果:7,12

       分析:

    1 //Add5 就相当于:
    2 function Add5(y) {
    3     return 5 + y;
    4 };
    5 //Add10 就相当于:
    6 function Add10(y) {
    7     return 10 + y;
    8 };

        Add5和Add10都是闭包,但是它们存储了不同的环境; Add5的环境中x是5,在Add10的环境中x是10;

    4. 闭包的使用场景

      1)  匿名自执行函数:(html中元素事件初始化)

        EX5:

     1 <head>
     2     <title></title>
     3     <script type="text/javascript">
     4         window.onload = function() {
     5             for (var i = 1; i < 4; i++) {
     6                 var li = document.getElementById("a" + i);
     7                 li.onclick = (function(i) {//匿名自执行函数
     8                     return function() {
     9                         alert(i);
    10                     }
    11                 })(i);
    12             }
    13         }
    14 
    15     </script>
    16 </head>
    17 <body>
    18     <ul>
    19         <li id="a1">a1</li>
    20         <li id="a2">a2</li>
    21         <li id="a3">a3</li>
    22     </ul>
    23 </body>

        效果:点击a1,弹出a1;点击a2,弹出a2;点击a3,弹出a3;

        2)    实现封装,从而实现了从函数外部读取函数内部的局部变量;

        EX6:

     1         var person = function () {
     2             var name = "world";//变量作用域为函数内部,外部无法访问    
     3             return {
     4                 getName: function () {
     5                     return name;
     6                 },
     7                 setName: function (newName) {
     8                     name = newName;
     9                 }
    10             }
    11         }();
    12         alert(person.name);//直接访问,结果为undefined    
    13         alert(person.getName());//结果:world
    14         person.setName("hello");
    15         alert(person.getName());//结果:hello

      3)   模拟面向对象中的对象;

          不同的对象拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
          我们可以模拟出这样的机制。

       EX7:

     1         function Person(){    
     2             var name = "default";       
     3        
     4             return {    
     5                 getName : function(){    
     6                     return name;    
     7                 },    
     8                 setName : function(newName){    
     9                     name = newName;    
    10                 }    
    11             }    
    12         };    
    13      
    14         var hello = Person();
    15         print(hello .getName()); //结果:   default 
    16         hello .setName("hello");    
    17         print(hello .getName());  //结果:   hello
    18      
    19         var world = Person();
    20         print(world .getName());   //结果:   default 
    21         world .setName("world");    
    22         print(world .getName());    //结果:   world 

      

    5. 常见错误

      EX8-1:

     1 var result = [];
     2 function play() {
     3     var i = 0;
     4     for (; i < 3; i = i + 1) {
     5         result[i] = function () {
     6             alert(i)
     7         }
     8     }
     9 };
    10 play();
    11 result[0](); //结果: 3
    12 result[1](); //结果: 3
    13 result[2](); //结果: 3

      EX6中,本希望play函数中的变量i被内部循环的函数使用,而实际上,只能获得该变量最后保留的值,也就是说.闭包中所记录的变量,只是对这个变量的一个引用,而非变量的值,当这个变量被改变了,

    闭包里获取到的变量值,也会被改变.

    解决的方法之一,是让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里.然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:

       EX8-2:

     1 var result = [];
     2 function play() {
     3     var i = 0;
     4     for (; i < 3; i = i + 1) {
     5         result[i] = (function (j) {
     6             return function () {
     7                 alert(j);
     8             };
     9         })(i);
    10     }
    11 };
    12 play();
    13 result[0](); //结果: 0
    14 result[1](); //结果: 1
    15 result[2](); //结果: 2

    性能考虑:

       EX9-1:

     1 function MyObject(name, message) {
     2   this.name = name.toString();
     3   this.message = message.toString();
     4   this.getName = function() {
     5     return this.name;
     6   };
     7 
     8   this.getMessage = function() {
     9     return this.message;
    10   };
    11 }

       分析:每new一个MyObject,getName和getMessage就会copy一次,影响性能;

       利用闭包对EX9-1进行改进:

      EX9-2:

     1 function MyObject(name, message) {
     2     this.name = name.toString();
     3     this.message = message.toString();
     4 }
     5 (function() {
     6     this.getName = function() {
     7         return this.name;
     8     };
     9     this.getMessage = function() {
    10         return this.message;
    11     };
    12 }).call(MyObject.prototype);

       分析:无论new多少个MyObject,getName和getMessage只copy一次;

    总结:

      在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据

    时,我们可以通过外面再包裹一层函数形成闭包来解决。

      当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露。

      ------

      此片随笔也是参考了各位前辈们的讲义,还有很多知识点等待学习研究,欢迎指正!

  • 相关阅读:
    【转载,待整理】初学 springmvc整合shiro
    【转载并整理】javaweb单点登录
    【转载】linux 测试机器端口连通性方法
    Intellij idea 复制粘贴查找快捷键失效
    intellij 打开node项目 一直停留在scanning files to index....,或跳出内存不够的提示框
    【转载】Hibernate之hbm.xml集合映射的使用(Set集合映射,list集合映射,Map集合映射)
    【转载并整理】mysql 1293错误 建表两个timestamp
    作用域与闭包:this,var
    在MongoDB中使用JOIN操作
    linux下用top命令查看cpu利用率超过100%
  • 原文地址:https://www.cnblogs.com/willingtolove/p/4745889.html
Copyright © 2011-2022 走看看