重新理解闭包

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重新理解闭包相关的知识,希望对你有一定的参考价值。

 官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点:
    1.作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
    2.一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
    简单的说,javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

   function closure(){
        var str = "I‘m a part variable.";
        return function(){
            alert(str);
        } 
    }
    var fObj = closure();
    fObj();

   在上面代码中,str是定义在函数closure中局部变量,若strclosure函数调用完成以后不能再被访问,则在函数执行完成后str将被释放。但是由于函数closure返回了一个内部函数,且这个返回的函数引用了str变量,导致了str可能会在closure函数执行完成以后还会被引用,所以str所占用的资源不会被回收。这样closure就形成了一个闭包。

 

事实上,当我们给自定义的javascript类中的私有变量添加get/set函数时,已经用到了闭包。 

   function Class1(){
       var vari;
       this.getVari = function(){return vari;}
       this.setVari = function(_vari){vari = _vari;}
   } 
   var cls = new Class1();
   cls.setVari("test variable");
   alert(cls.getVari());

 在这个例子中,构造器中定义了变量vari,构造器执行结束后如果vari没有被其他外部能够应用的成员方法所引用时,这个变量将被回收。但是我们给它定义了get/set函数以后,由于这两个内部函数可以被包含他们的函数Class1(类)的外部所访问,且变量vari被这两个函数所引用,因此vari不会被回收。这样就形成了闭包。

Javascript垃圾回收的原则是:如果一个对象不再被引用,那么这个对象会被垃圾回收器回收。如果两个对象无干扰的互相引用,那么这两个对象也会被回收。

 

闭包在javascript编程中有重要的作用,如果使用恰当,可以为某些问题的解决带来很多方便。但是如果使用不恰当,也会带来很多麻烦。  

 

1.为执行的函数提供参数。
    setTimeout可以延迟执行某个函数,原型如下: setTimeout(code,millisec)。其中第一个参数为需要执行的函数或者代码,第二个参数是延迟的毫秒数。常见用法:

   function sayHello(){
        alert("hello world");
   }
   setTimeout(sayHello,1000);

  但是可能我们会需要一个通用一些的函数,而不仅仅是只会说“hello world”。我们写一个say()函数,给say传递一个需要说的内容的参数。但是在setTimeout中我们无法给需要延迟执行的函数传递参数。使用闭包,我们可以这样做:

 
   function say(words){
       return function(){
           alert(words)
       }
   }   
   setTimeout(say("hello word"),1000);
   setTimeout(say("I‘m hungry,I‘m need some food!"),2000);   
 

    这样可以在调用setTimeout时给需要延迟执行的函数传递参数了。 

 

2.将对象的方法与其他实例关联

比如我们在做一个多文件上传的程序,要求可以动态添加和删除文件: 

 
       function addFile(){
          var fileInput = document.createElement("input");
          fileInput.type = "file"; 
          fileInput.name = "file";
           
          var  btnDel = document.createElement("input");
          btnDel.type = "button";
          btnDel.value = "删除";
          
          btnDel.onclick = (function(){
                //删除文件
                return function(){
                    var c = _("fileContent");
                    c.removeChild(fileInput);
                    c.removeChild(btnDel);
                }
          })();
          
          var fContent = _("fileContent")
          fContent.appendChild(fileInput);
          fContent.appendChild(btnDel);
       }
 

  在这个例子中,我们在删除按钮的点击时间关联的函数中删除对应的上传表单和按钮本身。由于表单和按钮都是动态生成,在事先并不知道删除按钮点击后需要删除的内容是什么。但是我们可以使用javascript闭包的特性,在上传表单和按钮生成的时候将按钮的点击事件与上传表单实例及和按钮实例进行关联。

 

3.模拟静态私有变量

Javascript本身并不支持面向对象的特性。但是我们可以通过javascript的一些特性模拟实现javascript面向对象。
    比如我们需要定义一个类,并且模拟静态私有变量。

 
   var Class2 = (function(){
       var s_var = 0;      //静态私有变量
       return function(){
            this.getInstanceCount = function(){
                return s_var; 
            }
           //constructor
          s_var++;  
       }
   })()
   
   var cls1 = new Class2();
   alert(cls1.getInstanceCount());   //1
   
   var cls2 = new Class2();
   alert(cls1.getInstanceCount());   //2
   alert(cls2.getInstanceCount());   //2
   
   var cls3 = new Class2();
   alert(cls1.getInstanceCount());   //3
   alert(cls3.getInstanceCount());   //3
 

    在这个例子中,我们用s_var记录Class2被实例化的次数,使用闭包,我们可以将s_var模拟为一个静态私有变量,每次Class2被实例化的时候将s_var加1。

上例中我们使用了这样形式的一段代码,其中定义在外层函数内,内层函数外的成员类似于静态成员。所以这样形式的代码我们可以把他叫做“静态封装环境”。

    (function(){     
       return function(){
       }     
    })()

 

原文地址:

https://www.cnblogs.com/zhangle/archive/2010/07/02/1770206.html

以上是关于重新理解闭包的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript之闭包(重新认识)

闭包的讲解与简单实用(重新理解)

JS---闭包

重新理解js的执行环境和闭包

理解JS闭包——以计数器为例

经典js闭包----对《大部分人都会做错的经典JS闭包面试题》的理解