Learning Closure:学习闭包

 

先让我回忆一下第一次见到这个词时的情景:那是在2006年的某天,当时一个朋友像打了鸡血一样兴奋的给我推荐了一个5句话就可以创建一个网站的框架——Rails,看完演示后我也像打了鸡血一样兴奋的瞟了一眼这个框架背后的语言——Ruby。从此,我知道了一个让我感到陌生、高深甚至深不可测到可以装B的词,这就是闭包。

让我惭愧的是,我真的是时常拿这个词出去装B,但是却长时间不知道它的真正含义。甚至一度觉得周围跟我提起这个词的人和我是一样zhuangbility的。

就这样,我到了2010年。闭包已经不再是一个让人鸡血沸腾的词语了,我觉得是时候冷静的了解一下这个概念了,所以我记下了这篇笔记。

首先记录一个比较靠谱的定义:

闭包(Closure),词法闭包(Lexical Closure)的简称,是由函数和与其相关的引用环境组合而成的实体。

当然,网上还会有很多其它的说法和定义,我只是在这里记录了一条我觉得比较靠谱的。即使如此,当我第一次看到上面这句话的时候也还是一头雾水。所以如果你看完这句话就明白什么意思了,那么下面就不需要浪费时间了;但是如果你看完后也和我一样迷糊,不要着急,让我们慢慢的来,下面的解释会让我们更加“迷糊”。

我坚信例子是解释一切问题最好的方式。但是为难我的是,我应该用什么语言做例子来演示这个问题呢?现在支持闭包的语言有很多,python、Ruby、perl、Lua以及JavaScript等等,当然熟悉这些语言的人估计也不会在这里浪费他们的时间,不熟悉闭包的人可能恰好也不熟悉这些语言。我觉得用一个不熟悉的语言来演示一个晦涩的概念,应该属于越摸越黑的那种。算了,我们一会儿再说这个事情,让我冷静一会儿。

函数

我们先简单的聊聊函数吧,因为闭包这个概念貌似最早来自于函数式编程(FP,Functional Programming),当然也不能深聊,毕竟第一这个笔记的主旨不在这里,其次聊多了我就露馅儿了。->_<-

说说我对函数的理解(MD,维基百科怎么打不开了,抄的地儿都没有了)。还记得数学课上的那个 f(x)=x^{2} + 1 ,这个就是函数啊。

明白了吧!以上就是我对函数的理解。

请盯着那个等式看一会儿,脑子里想想你最熟悉的编程语言。那个f是不是像我们的函数名,那个x就是函数的参数(输入),而 x^{2} + 1 就是它的结果(输出)。我们程序中每一个函数不都遵循这样一个原则么——有零个或多个输入,有一个输出。虽然我不能妄断方法和函数的区别,但是我们所说的方法不也是这样么。

接下来就是我们对 f(x)的使用了。我们可以这么用: f(y)=2f(x)+y。看着这个函数思考一下,我们程序中的函数是否也可以这么写呢?(当然,我说的不是格式,哥,你懂的!)

最后,对于我们的 f(x)来说,如果我们输入的值是固定的,那么我们总能得到一个相同的输出。比如我们输入2,那么我们总能得到5。

好了,让我们来看看函数比较抽象的概念吧:

在数学领域,函数是一种关系,这种关系使一个集合里的每一个元素对应到另一个集合里的唯一元素。函数是将唯一的输出值赋予每一输入的"法则"。这一"法则"可以用函数表达式、数学关系,或者一个将输入值与输出值对应列出的简单表格来表示。函数最重要的性质是其决定性,即同一输入总是对应同一输出(注意, 反之未必成立)。从这种视角,可以将函数看做"机器"或者"黑盒",它将有效的输入值变换为唯一的输出值。通常将输入值称做函数的参数,将输出值称做函数的值。

至于函数式编程,真的不是一句两句可以说清楚的。有兴趣的话可以google一下。总之我们的程序也可以像上面的数学函数那样来编写和使用。

闭包

思来想去明白一个事儿,那就是既然哪个语言都不熟悉,也就没有必要挑来挑去了。我就随便摘抄了哈!!!

先看一个例子(JavaScript):

  1. function makeFunc() {  
  2.   var name = "Closure";  
  3.   function displayName() {  
  4.     alert(name);  
  5.   }  
  6.   return displayName;  
  7. }  
  8.   
  9. var myFunc = makeFunc();  
  10. myFunc(); 

应该不难懂的。函数 makeFunc 定义了一个局部变量 name 和一个函数 displayName,并且 displayName 是一个内部函数,它在 makeFunc 函数中被定义,仅在函数内部可见。displayName 函数内部没有定义任何局部变量,它仅仅使用了外部函数中的变量。makeFunc 返回了定义好的 displayName 函数。紧接着我们调用 makeFunc 并将其返回的函数赋值给了 myFunc 变量。最后我们调用 myFunc 来得到结果。

不管你是不是理解里面的运行机制,但是答案我们都可以猜想的出—— name 变量的值Closure被显示了出来。也许这个例子太简单了,简单到我们想当然的认为结果就是显示 name 变量的值。但是慢着,让我们深究一下就有意思了。

注意一下那个局部变量 name,看是不是能让你想起些什么。既然我们称 name 为局部变量,也就是说它的作用范围(可见性)应该在 makeFunc 函数内部,只有当我们执行 makeFunc 的时候它才存在,当 makeFunc 函数执行完毕之后它就应该不存在了,为什么我们还可以看到我们想要的结果呢?

这就是因为我们的 myFunc 成为了闭包(Closure)。现在再让我们回想以下我们一开始提到的那个靠谱的概念,闭包绑定了两个东西:函数和函数定义时的环境。这个环境包括所有对于函数定义时可见的局部变量。所以,当我们创建 myFunc 时,它将 displayName 函数和局部变量 name 包了起来,形成了我们说的闭包。

如果我们对上面这个例子还有说不清道不明的感觉的话,那我们就再来一个清楚的例子(Lua):

  1. function do10times(fn)   
  2.   for i = 0,9 do
  3.     fn(i)
  4.   end  
  5. end 

  6. sum = 0  
  7. function addsum(i)
  8.   sum = sum + 1
  9. end
  10. do10times(addsum)
  11. print(sum)

这里我们看到,函数 addsum 被传递给函数 do10times,被并在 do10times 中被调用10次。不难看出 addsum 实际的执行点在 do10times 内部,它要访问非局部变量 sum,而 do10times 并不在 sum 的作用域内。这看起来也是无法正常执行的。


再一次,闭包帮了我们。当我们传递 addsum 时,就已经创建了闭包,它将 sum 和 addsum 包了起来形成了一个闭合的作用域。当我们把这个闭合的作用域当作一个整体来调用的时候,先把其中的引用环境覆盖到当前的引用环境上,然后执行具体代码,并在调用结束后恢复原来的引用环境。这样就保证了函数定义和执行时的引用环境是相同的,问题也就迎刃而解了。

 

引用一句话总结一下:对象是附有行为的数据,而闭包是附有数据的行为。

闭包的应用

我觉得我应该说明白什么是闭包了,下面该写一下这个被说的沸沸扬扬的东西到底有什么用处了。

嗯。。。怎么说呢,关于闭包的应用我第一个能够想到的就是重构——没错,就是那个让你怎么做都觉得不对的重构。其实就我个人来说,平时最头疼的是感觉手上掌握的工具不够或者还不犀利。当然,闭包可以为我们的工具箱增加一定的份量。我们可以用它来重构出更加紧凑和抽象的代码。

第二个能想到的是循环遍历。我觉得大家对这个应该都不会陌生。还是举个简单的例子来说吧。不知道大家平时在做项目的时候是否碰到过重复重复再重复写循环语句的时候呢?这个时候用Java来做的话,就不得不四处编写for、while语句或者使用iterator。但是如果我们用支持闭包的语言的话,比如Ruby语言,就会方便很多,看看下面的代码:

  1. nums = [10,3,22,34,17]  
  2. sum = 0  
  3. nums.each{|n| sum += n}
  4. print sum

另外再来一个更直观的例子:遍历数据库查询结果。用过Java的童鞋好好想想Java是如何从数据库中取出ResultSet并且如何遍历的吧。如果谈及这个,估计Ruby的童鞋该笑了。不是没有原因的,看看下面的代码就知道了:

  1. require 'mysql'
  2. db=Mysql.new("localhost", "root", "password")
  3. db.select_db("database")

  4. result = db.query "select * from words"
  5. result.each {|row| do_something_with_row}
  6. db.close

第三个能够想到的是关于资源和事务的管理。比如当我们想要读取文件并对文件内容进行一些处理,这个时候打开文件、关闭文件以及过程中异常的处理其实都是可抽象的。也就是说不管我们对文件做什么处理,打开、关闭和异常处理都是相同的。如果你是文件类库的作者的话,就应该将对资源的管理抽象出来,让用户更多的关注文件的处理。看看Ruby的代码就知道了:

  1. File.open(name) {|file| process_file(f)}

看了上面的代码,你还会为处理文件而感到头疼么?反正我是不会了。

关于闭包的作用还有很多很多,大家可以去慢慢的体会。总之,当我们掌握了闭包这个工具之后,它会给我们日常的coding工作带来很多的好处。

写在最后

时间可以沉淀很多的东西,尤其是在IT行业中。很多的概念或者技术都曾被炒的沸沸扬扬,但许多到最后都销声匿迹了。不过我们不能完全忘记它们,因为没有哪一项技术天生就是坏的,它的演化和发展肯定会对未来产生影响。闭包很早就存在了,直到近几年才广泛被大家接受。甚至Java 7也因为闭包的加入而拖延了它的发布日期。所以当一项技术逐渐成熟时,我们不能墨守成规的对其视而不见,应该及时的了解它们并拥抱它们。

Java语言现在还不支持闭包,Java 7也是前不久才宣布支持闭包的,但是我们现在可以用匿名内部类来模拟闭包的实现。可惜模拟终归模拟,会有许多的限制。那么如果Java的程序员像用闭包的话该怎么办呢?其实不支持闭包也仅仅是Java语言,在Java虚拟机上已经有能够让我们随心所欲使用这个工具的语言了——对,就是Groovy和Scala。当然还有JRuby和Jython。总之我们的选择有很多,而我们要做的就是接受它们。


参考及引用资料:

  1. Bruce Tate:跨越边界: 闭包 
  2. 李文浩:闭包的概念、形式与应用
  3. Martin Flower:Closure

文中大部分的代码来自于以上文章。

另注:该文章属于学习笔记类,很多东西并非作者原创,绝无侵犯版权之意,特此声明。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值