您的位置:首页 > 其它

如何理解闭包(摘录网友的文章)

2016-07-27 11:03 260 查看
    如何理解闭包?下面是网友的一些机理,觉得写得不错,故摘录下来参考:
    

维基百科上对闭包的解释就很经典:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。Peter J. Landin 在1964年将术语闭包定义为一种包含环境成分和控制成分的实体。

下面是网友理解的闭包概念。

先看看数学上的闭包。

(1,5) 是一个区间,但对这个区间做分析、计算什么的,经常会用到1和5这两个不属于这个区间的值,[1,5]就是(1,5)的闭包。

在生活上,我们办事情,找A部门,A部门说,你先得找B部门盖个章,B部门说,你先得找C部门盖个章,C部门说,这个东西不是我们的职权范围…… 踢皮球,这就是非闭包。闭包就是负责到底,你找到A部门,A部门接待的那个人负责到底,他/她去协调B部门和C部门。

在工程上,闭包就是项目经理,负责调度项目所需要的资源。老板、客户有什么事情,直接找项目经理即可,不用再去找其它的人。

在程序语言中,闭包就是一种语法糖,它以很自然的形式,把我们的目的和我们的目的所涉及的资源全给自动打包在一起,以某种自然、尽量不让人误解的方式让人来使用。至于其具体实现,我个人意见,在不影响使用的情况下,不求甚解即可。在很多情况下,需要在一段代码里去访问外部的局部变量,不提供这种语法糖,需要写非常多的代码,有了闭包这个语法糖,就不用写这么多代码,自然而然的就用了。

这样一来,可以把闭包从一个语法机制提升为一种设计原则:闭包是从用户角度考虑的一种设计概念,它基于对上下文的分析,把龌龊的事情、复杂的事情和外部环境交互的事情都自己做了,留给用户一个很自然的接口。

在这个原则下,函数式语言中,那种所谓的闭包只是一种“闭包”,还有大量的其它类型的“闭包”等待发现和实现。

下面举出一些闭包设计原则的正例和反例。

正例:Flex中的数据绑定语法就是一种“闭包”。x="{b.num + c.num}",对于这个语法,编译器自动去上下文中寻找叫 b 和 c 的变量,然后再找他们内部 num 变量,如果他们都是可绑定的话,则自动给它们添加上绑定链,当 b, c, num 等有任一变动时,更新 x 的值。

反例:Winform 中的设计就违反了闭包原则,当不是在该UI线程中,更新某些控件的值时,会抛出异常。只能去invoke调用,而invoke的接口很难用,相信很多人对这东东极其反感。

闭包不一定是语法糖。当我们不能直接扩展编译器时,我们就无法增加语法糖来实现闭包机制,这时,就要用现有的语言机制来实现了。 下面,我们来对winform的invoke方法进行改造,使它满足闭包原则。下面是代码:

public class ControlFuncContext 
    { 
        public Control Control { get; private set; } 
        public Delegate Delegate { get; private set; }
        public ControlFuncContext(Control ctl, Delegate d) 
        { 
            this.Control = ctl; 
            this.Delegate = d; 
        }
        public void Invoke0() 
        { 
            if (Control.IsHandleCreated == true) 
            { 
                try 
                { 
                    Delegate.DynamicInvoke(); 
                } 
                catch(ObjectDisposedException ex) 
                { 
                } 
            } 
        }
        public void Invoke1<T>(T obj) 
        { 
            if (Control.IsHandleCreated == true) 
            { 
                try 
                { 
                    Delegate.DynamicInvoke(obj); 
                } 
                catch (ObjectDisposedException ex) 
                { 
                } 
            } 
        }
        public void Invoke2<T0,T1>(T0 obj0, T1 obj1) 
        { 
            if (Control.IsHandleCreated == true) 
            { 
                try 
                { 
                    Delegate.DynamicInvoke(obj0, obj1); 
                } 
                catch (ObjectDisposedException ex) 
                { 
                } 
            } 
        } 
    }
    public static class FormClassHelper 
    {
        public static void InvokeAction(this Control ctl, Action action) 
        { 
            if (ctl.IsHandleCreated == true) 
            { 
                ControlFuncContext fc = new ControlFuncContext(ctl, action); 
                ctl.Invoke(new Action(fc.Invoke0)); 
            } 
        }
        public static void InvokeAction<T>(this Control ctl, Action<T> action, T obj) 
        { 
            if (ctl.IsHandleCreated == true) 
            { 
                ControlFuncContext fc = new ControlFuncContext(ctl, action); 
                ctl.Invoke(new Action<T>(fc.Invoke1<T>), obj); 
            } 
        }
        public static void InvokeAction<T0, T1>(this Control ctl, Action<T0, T1> action, T0 obj0, T1 obj1) 
        { 
            if (ctl.IsHandleCreated == true) 
            { 
                ControlFuncContext fc = new ControlFuncContext(ctl, action); 
                ctl.Invoke(new Action<T0, T1>(fc.Invoke2<T0, T1>), obj0, obj1); 
            } 
        } 
    }

使用起来很简单,直接调用扩展方法 InvokeAction 即可,不必去考虑跨线程还是不跨线程这些“环境因素”,跨线程调用,我们已经通过用户不必知晓的方式,把它封装起来了。

再举个例子,写程序经常需要这样一个功能:打开一个图像文件,然后进行处理。正常写法很麻烦,比如,那个filter格式就很容易忘记,那么,我们就把它闭包化,把不该让用户知道,不该让用户敲键盘的都给它封装起来:

public static void OpenFile(this Form element, Action<string> callbackOnFilePath, String filter = "所有文件|*.*")
        {
            String filePath;
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = filter;
            dlg.FileOk += (object sender, CancelEventArgs e) =>
            {
                filePath = dlg.FileName;
if (callbackOnFilePath != null)
                    callbackOnFilePath(filePath);
            };
            dlg.ShowDialog();
        }
public static void OpenImageFile(this Form element, Action<String> callbackOnFilePath, String filter = "图像文件|*.bmp;*.jpg;*.gif;*.png")
        {
            OpenFile(element, callbackOnFilePath, filter);
        }
</string>

再举一个例子,这个例子是as3中的。在Flex中,控件有一个callLater 方法,在下一帧时进行调用。这个方法非常有用,很多时候,非Flex项目也需要这样的一个方法。下面,我们进行模拟:

package orc.utils 

    import flash.display.Stage; 
    import flash.events.Event;
    public class CallLaterHelper 
    { 
        public function CallLaterHelper(stage:Stage, callback:Function) 
        { 
            this.callback = callback; 
            this.stage = stage;
            stage.addEventListener(Event.ENTER_FRAME, onStageEnterFrame); 
        } 
        private var stage:Stage; 
        private var callback:Function; 
        private function onStageEnterFrame(event:Event):void 
        { 
            stage.removeEventListener(Event.ENTER_FRAME, onStageEnterFrame); 
            if(callback != null) 
            { 
                callback(); 
            } 
        } 
    } 
}

然后在基础控件中,提供callLater方法:

public function callLater(callback:Function):void 

    new CallLaterHelper(this.stage,callback); 
}

总结:
闭包是一种设计原则,它通过分析上下文,来简化用户的调用,让用户在不知晓的情况下,达到他的目的;
网上主流的对闭包剖析的文章实际上是和闭包原则反向而驰的,如果需要知道闭包细节才能用好的话,这个闭包是设计失败的;
===================================================================================

以上是网友的理解,看完上面的解析在看官方的解析就会容易理解很多。

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

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
  2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
  简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

2、闭包的几种写法和用法
 
首先要明白,在JS中一切都是对象,函数是对象的一种。下面先来看一下闭包的5种写法,简单理解一下什么是闭包。后面会具体解释。
 

//第1种写法  
function Circle(r) {  
      this.r = r;  
}  
Circle.PI = 3.14159;  
Circle.prototype.area = function() {  
  return Circle.PI * this.r * this.r;  
}  
  
var c = new Circle(1.0);     
alert(c.area());   

这种写法没什么特别的,只是给函数添加一些属性。
 

//第2种写法  
var Circle = function() {  
   var obj = new Object();  
   obj.PI = 3.14159;  
     
   obj.area = function( r ) {  
       return this.PI * r * r;  
   }  
   return obj;  
}  
  
var c = new Circle();  
alert( c.area( 1.0 ) );  

这种写法是声明一个变量,将一个函数当作值赋给变量。
 

//第3种写法  
var Circle = new Object();  
Circle.PI = 3.14159;  
Circle.Area = function( r ) {  
       return this.PI * r * r;  
}  
  
alert( Circle.Area( 1.0 ) );  

这种方法最好理解,就是new 一个对象,然后给对象添加属性和方法。
 

//第4种写法  
var Circle={  
   "PI":3.14159,  
 "area":function(r){  
          return this.PI * r * r;  
        }  
};  
alert( Circle.area(1.0) );  
复制代码
这种方法使用较多,也最为方便。var obj = {}就是声明一个空的对象。
 
//第5种写法  
var Circle = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}");  
  
alert( (new Circle()).area(1.0) );  
说实话,这种写法我是没用过,大家可以参考一下。
 
总的来说,上面几种方法,第2中和第4中较为常见,大家可以根据习惯选择。

上面代码中出现了JS中常用的Prototype,那么Prototype有什么用呢?下面我们来看一下:
 
复制代码
    var dom = function(){
        
    };
 
    dom.Show = function(){
        alert("Show Message");
    };
    
    dom.prototype.Display = function(){
        alert("Property Message");
    };
 
    dom.Display(); //error
    dom.Show();  
    var d = new dom();
    d.Display();
    d.Show(); //error
复制代码
我们首先声明一个变量,将一个函数赋给他,因为在Javascript中每个函数都有一个Portotype属性,而对象没有。添加两个方法,分别直接添加和添加打破Prototype上面,来看下调用情况。分析结果如下:
 
   1、不使用prototype属性定义的对象方法,是静态方法,只能直接用类名进行调用!另外,此静态方法中无法使用this变量来调用对象其他的属性!
   2、使用prototype属性定义的对象方法,是非静态方法,只有在实例化后才能使用!其方法内部可以this来引用对象自身中的其他属性!
 
 
 
下面我们再来看一段代码:
 

var dom = function(){
        var Name = "Default";
        this.Sex = "Boy";
        this.success = function(){
            alert("Success");
        };
    };
 
    alert(dom.Name);
    alert(dom.Sex);

大家先看看,会显示什么呢? 答案是两个都显示Undefined,为什么呢?这是由于在Javascript中每个function都会形成一个作用域,而这些变量声明在函数中,所以就处于这个函数的作用域中,外部是无法访问的。要想访问变量,就必须new一个实例出来。
 

var html =
{
        Name:'Object',
        Success:function(){
            this.Say = function(){
                    alert("Hello,world");
            };
            alert("Obj Success");
        }
    };

再来看看这种写法,其实这是Javascript的一个"语法糖",这种写法相当于:
 

    var html = new Object();
    html.Name = 'Object';
    html.Success = function(){
            this.Say = function(){
                    alert("Hello,world");
            };
            alert("Obj Success");

变量html是一个对象,不是函数,所以没有Prototype属性,其方法也都是公有方法,html不能被实例化。否则会出现如下错误:

但是他可以作为值赋给其它变量,如var o = html; 我们可以这样使用它:
 
    alert(html.Name);
    html.Success();
说到这里,完了吗?细心的人会问,怎么访问Success方法中的Say方法呢?是html.Success.Say()吗?
 
当然不是,上面刚说过由于作用域的限制,是访问不到的。所以要用下面的方法访问:
 
复制代码
var s = new html.Success();
s.Say();

//还可以写到外面
html.Success.prototype.Show = function(){
    alert("HaHa");
};
var s = new html.Success();
s.Show();

二、Javascript闭包的用途                                                                  
 
事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
 
1、匿名自执行函数
 
  我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,
比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,
比如UI的初始化,那么我们可以使用闭包:
 
复制代码
var data= {    
    table : [],    
    tree : {}    
};    
     
(function(dm){    
    for(var i = 0; i < dm.table.rows; i++){    
       var row = dm.table.rows[i];    
       for(var j = 0; j < row.cells; i++){    
           drawCell(i, j);    
       }    
    }    
       
})(data);   
复制代码
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。

2、结果缓存
 
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,
 
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

3、封装
 
复制代码
var person = function(){    
    //变量作用域为函数内部,外部无法访问    
    var name = "default";       
       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
}();    
     
print(person.name);//直接访问,结果为undefined    
print(person.getName());    
person.setName("abruzzi");    
print(person.getName());    
   
得到结果如下:  
   
undefined  
default  
abruzzi  

4、实现类和继承
 
复制代码
function Person(){    
    var name = "default";       
       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
    };   
 
    var p = new Person();
    p.setName("Tom");
    alert(p.getName());
 
    var Jack = function(){};
    //继承自Person
    Jack.prototype = new Person();
    //添加私有方法
    Jack.prototype.Say = function(){
        alert("Hello,my name is Jack");
    };
    var j = new Jack();
    j.setName("Jack");
    j.Say();
    alert(j.getName());

我们定义了Person,它就像一个类,我们new一个Person对象,访问它的方法。
 
下面我们定义了Jack,继承Person,并添加自己的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  闭包