1. 何为闭包
在学习函数式语言、动态语言时会经常接触到一个概念——闭包,例如python、scala中都有对闭包的支持。
翻看网上各种资料,对于闭包的解释都比较晦涩、学术。
比如百科中这段解释:“在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,
是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。”
总结这段话,闭包的定义有两种:
(1)引用了自由变量的函数(自由变量是指除局部变量以外的变量);
(2)函数和与其相关的引用环境组合而成的实体。
这两种定义是不同,(1)认为闭包就是函数,与普通函数的区别是引用了自由变量,(2)认为闭包是一个实体,不同于函数
在学习python时对闭包的理解是一个函数绑定了定义它时的上下文,(2)更符合闭包实际应用时直觉。
2. 创建闭包
下面从scala、python、javascript三种语言来说明如何创建一个闭包。
只简单介绍如何创建闭包,并不对三种语言闭包实现差异作比较。
2.1. scala闭包
但在外部通过函数f也可以访问函数a的私有变量,就像拥有了类的特性。
2.2. python闭包
从上面的代码就可以更明显的看出闭包让函数具有了类的特性,其实闭包是具有数据的行为,而对象是具有行为的数据。
2.3. javascript闭包
从上面可以看出三种语言闭包功能的实现大同小异,本质上提供一种函数外部访问局部变量的能力。
3. 闭包的应用场景
其实在学习一个技术时,最主要是它的应用场景,因为只学不用的话会很快忘记这个技术。
3.1. 细粒度的模块化
对列表的遍历操作,使用频率非常高,比如打印列表中每个对象、列表求和、列表乘积。
在C语言中实现这样的功能需要每次都利用for来遍历数组,然后写出具体的操作逻辑。
缺点是明显的,不断重复遍历语句,代码文件变得冗长。
在面向对象语言中,比如java,实现这样的功能也是需要重复遍历列表的。当然也可以为列表封装一个类,
里面定义了针对数据的操作逻辑,sum、multiply、print等。当每次new一个类时,每个类都有自己的成员方法。
这样做的缺点是:内存占用较多,因为每个对象维护了自己的成员方法;扩展性较差,如果有其他的类似操作,比如为列表中每个值
加上固定值,那么就要修改类的定义了。
其实对上述操作抽象出来公共的行为是遍历操作,那么只需要建立一个遍历行为功能,所有其他遍历时的具体操作,由调用时来指定即可。
那么函数式语言中的实现如下
上述的foreach遍历操作是一个公共的操作,在调用它时,传递了一个求和函数。
这可能与回调函数非常相似,但是请注意,在这个例子中,回调函数引用了定义它的当前环境的变量sum,并且这种对sum的改变直接反应在
当前的上下文中,不需要return操作。这种功能是回调函数不具备的。
扩展性也较好,可以实现功能完全取决于在调用foreach时传递的函数功能。
3.2. 利用闭包模拟面向对象编程
在scala和python这种原生就支持面向对象编程的语言中来解释这个例子可能不太合适,
解释这个特性需要在不支持面向对象技术但支持闭包的语言中举例,例如javascript。
下面的实例利用闭包特性模拟一个类
使用这个类
当然闭包的应用场景远不止上述罗列出两点,由于目前没有接触过更多的闭包的使用实例,需要继续发现和探索。
4. 总结
可以看出闭包的特性是非常重要的,能帮助我们更优雅的实现某些功能,简化代码。当然闭包对于内存的消耗也是非常大的,需要保存函数的上下文,
目前在硬件的容量、性能越来越好的情况下,当然鼓励去更多使用闭包。