什么是闭包
闭包(Closure)是词法闭包(Lexical Closure)的简称
闭包的具体定义 多种说法 大体可以分为两类

1 闭包是符合一定条件的函数
闭包是在其词法上下文中引用了自由变量(自由变量是指除局部变量以外的变量)的函数

2 闭包是由函数和与其相关的引用环境组合而成的实体
在实现深约束(英文 binding 翻译为绑定)时 需要创建一个能显式表示引用环境的东西 并将它与相关的子程序捆绑在一起 这样捆绑起来的整体被称为闭包

两种定义一个认为闭包是函数 另一个认为闭包是函数和引用环境组成的整体
第二种说法更确切 闭包只是在形式和表现上像函数
实际上不是函数 函数是一些可执行的代码 这些代码在函数被定义后就确定了 不会在执行时发生变化 所以一个函数只有一个实例
闭包在运行时可以有多个实例 不同的引用环境和相同的函数组合可以产生不同的实例

引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合 其中的约束是指一个变量的名字和其所代表的对象之间的联系 那么为什么要把引用环境与函数组合起来呢?
这主要是因为在支持嵌套作用域的语言中 有时不能简单直接地确定函数的引用环境

这样的语言一般具有这样的特性
函数是一阶值(First-class value) 即函数可以作为另一个函数的返回值或参数 还可以作为一个变量的值
函数可以嵌套定义 即在一个函数内部可以定义另一个函数

Lua 语言的语法比较接近伪代码 一段 Lua 的代码
1. 闭包示例     
function make_counter()
 local count = 0
 function inc_count()
  count = count + 1
        return count

  end
 return inc_count
end

c1 = make_counter()
c2 = make_counter()
print(c1()) --show 1
print(c2()) --show 1
函数 inc_count 定义在函数 make_counter 内部 并作为 make_counter 的返回值
变量 count 不是 inc_count 内的局部变量 按照最内嵌套作用域的规则 inc_count 中的 count 引用的是外层函数中的局部变量 count 接下来的代码中两次调用 make_counter()  并把返回值分别赋值给 c1 和 c2  然后又依次打印调用 c1 和 c2 所得到的返回值

当调用 make_counter 时 在其执行上下文中生成了局部变量 count 的实例 所以函数 inc_count 中的 count 引用的就是这个实例
但是 inc_count 并没有在此时被执行 而是作为返回值返回 当 make_counter 返回后 其执行上下文将失效 count 实例的生命周期也就结束了 在后面对 c1 和 c2 调用实际是对 inc_count 的调用 而此处并不在 count 的作用域中 这看起来是无法正确执行的

上面的例子说明了 把函数作为返回值时需要面对的问题 当把函数作为参数时 也存在相似的问题

下面的例子演示了把函数作为参数的情况
闭包示例2
    
function do10times(fn)
 for i = 0,9 do
 fn(i)
 end
end
 
sum = 0
function addsum(i)
 sum = sum + i
end
 
do10times(addsum)
print(sum) --45

函数 addsum 被传递给函数 do10times 被并在 do10times 中被调用10次
不难看出 addsum 实际的执行点在 do10times 内部 它要访问非局部变量 sum 而 do10times 并不在 sum 的作用域内 这看起来也是无法正常执行的
在这样的语言中 如果按照作用域规则在执行时确定一个函数的引用环境 那么这个引用环境可能和函数定义时不同 要想使这两段程序正常执行 在函数定义时捕获当时的引用环境 并与函数代码组合成一个整体 当把这个整体当作函数调用时 先把其中的引用环境覆盖到当前的引用环境上 然后执行具体代码 并在调用结束后恢复原来的引用环境
保证函数定义和执行时的引用环境是相同的 这种由引用环境与函数代码组成的实体就是闭包

当然如果编译器或解释器能够确定一个函数在定义和运行时的引用环境是相同的(一个函数中没有自由变量时 引用环境不会发生变化) 那就没有必要把引用环境和代码组合起来了 这时只需要传递普通的函数就可以了

结论
闭包不是函数 只是行为和函数相似 不是所有被传递的函数都需要转化为闭包 只有引用环境可能发生变化的函数才需要这样做

上面两个例子 代码中并没有通过名字来调用函数 inc_count 和 addsum 所以 不需要名字 以第一段代码为例 可以重写成下面这样
清单 3. 闭包示例3
 
    
function make_counter()
 local count = 0
 return function() --使用了匿名函数 使代码得到简化 不需要给一个不需要名字的函数取名字
 count = count + 1
 return count
 end
end
 
c1 = make_counter()
c2 = make_counter()
 
print(c1()) --1
print(c2()) --1

编程语言需要哪些特性来支持闭包呢 比较重要的条件 [不是必要的 具备这些条件能说明一个编程语言对闭包的支持较为完善]

函数是一阶值
函数可以嵌套定义
可以捕获引用环境 并把引用环境和函数代码组成一个可调用的实体
允许定义匿名函数

有些语言使用与函数定义不同的语法来定义这种能被传递的"函数" 如 Ruby 中的 Block 这实际上是语法糖 只是为了更容易定义匿名函数 本质上没有区别

对象是 有行为的数据 闭包是 有数据的行为

闭包的表现形式
各种语言 实现的闭包 不同的表现形式
 
JavaScript
清单 4
function addx(x) {
 return function(y) {return x+y;};
}
 
add8 = addx(8);
add9 = addx(9);
 
alert(add8(100));
alert(add9(100));

Ruby 中的闭包
清单 5
sum = 0
10.times{|n| sum += n}
print sum

10.times 表示调用对象10的 times 方法(注5) 紧跟在这个调用后面的大括号里面的部分就是Block 所谓 Block 是指紧跟在函数调用之后用大括号或 do/end 括起来的代码 Block 的开始部分(左大括号或 do)必须和函数调用在同一行 Block 也可以接受参数 参数列表必须用两个竖杠括起来放在最前面 Block 会被作为它前面的函数调用的参数 而在这个函数中可以使用关键字 yield 来调用该 Block 在这个例子中 10.times 会以数字0到9为参数调用 Block 10次

Block 实际上就是匿名函数 它可以被调用 可以捕获上下文 由于语法上要求 Block 必须出现在函数调用的后面 所以 Block 不能直接作为函数的的返回值 要想从一个函数中返回 Block 必须使用 proc 或 lambda 函数把 Block 转化为对象才行


Python 中的闭包

def addx(x):
 def adder (y): return x + y
 return adder
 
add8 = addx(8)
add9 = addx(9)
 
print add8(100)
print add9(100)

在 Python 中使用 def 来定义函数时 是必须有名字的 要想使用匿名函数 则需要使用lambda 语句 象下面的代码这样:
def addx(x):
 return lambda y: x + y
 
add8 = addx(8)
add9 = addx(9)
 
print add8(100)
print add9(100)


Perl 中的闭包

sub addx {
 my $x = shift;
 return sub { shift() + $x };
}
 
$add8 = addx(8);
$add9 = addx(9);
 
print $add8->(100);
print $add9->(100);
Lua 中的闭包

Lua 以其小巧和快速的特点受到游戏开发者的青睐  用来定制 UI 或作为插件语言 如果你玩过《魔兽世界》 那你对 Lua 一定不会感到陌生


Scheme 中的闭包
Scheme 是 Lisp 的一种方言 被 MIT 用作教学语言 Scheme 属于函数语言 虽然不像命令语言那么流行 却是很多黑客喜欢的语言 很多编程思想起源于函数语言 闭包就是其中之一 一般认为 Scheme 是第一个提供完整闭包支持的语言 下面是一个 Scheme 的例子:
(define (addx x)
 (lambda (y) (+ y x)))
 
(define add8 (addx 8))
(define add9 (addx 9))
 
(add8 100)
(add9 100)

闭包的应用

加强模块化
闭包有益于模块化编程 以简单的方式开发较小的模块 从而提高开发速度和程序的可复用性 和没有使用闭包的程序相比 使用闭包可将模块划分得更小 要计算一个数组中所有数字的和 这只需要循环遍历数组 把遍历到的数字加起来就行了 如果现在要计算所有元素的积呢?要打印所有的元素呢?解决这些问题都要对数组进行遍历 如果是在不支持闭包的语言中  不得不一次又一次重复地写循环语句 而这在支持闭包的语言中是不必要的 比如对数组求和的操作在 Ruby 中可以这样做:
nums = [10,3,22,34,17]
sum = 0
nums.each{|n| sum += n}
print sum

这种处理方法多少有点像熟悉的回调函数 不过要比回调函数写法更简单 功能更强大 因为在闭包里引用环境是函数定义时的环境 所以在闭包里改变引用环境中变量的值 直接就可以反映到它定义时的上下文中 这是通常的回调函数所不能做到的 这个例子说明闭包可以使把模块划分得更小

抽象
闭包是数据和行为的组合 这使得闭包具有较好抽象能力  闭包来模拟面向对象编程 函数 make_stack 用来生成 stack 对象 它的返回值是一个闭包 这个闭包作为一个 Dispatcher 当以 “push” 或 “pop” 为参数调用时 返回一个与函数 push 或 pop 相关联的闭包 进而可以操作 data 中的数据
 
function make_stack()
 
    local data = {};
    local last = -1;
 
    local function push(e)
        last = last + 1;
        data[last] = e;
    end
 
    local function pop()
        if last == -1 then
            return nil
        end
        last = last - 1
        return data[last+1]
    end
 
    return function (index)
        local tb = {push=push, pop=pop}
        return tb[index]
    end
end
 
s = make_stack()
 
s("push")("test0")
s("push")("test1")
s("push")("test2")
s("push")("test3")
 
print(s("pop")())
print(s("pop")())
print(s("pop")())


简化代码
在一个窗口上有一个按钮控件 当点击按钮时会产生事件 如果选择在按钮中处理这个事件 那就必须在按钮控件中保存处理这个事件时需要的各个对象的引用 另一种选择是把这个事件转发给父窗口 由父窗口来处理这个事件 或是使用监听者模式 无论哪种方式 编写代码都不太方便 甚至要借助一些工具来帮助生成事件处理的代码框架 用闭包来处理这个问题则比较方便 可以在生成按钮控件的同时就写下事件处理代码 比如在 Ruby 中可以这样写:
 
    
song = Song.new
start_button = MyButton.new("Start") { song.play }
stop_button = MyButton.new("Stop") { song.stop }
 
总结

闭包能优雅地解决很多问题 很多主流语言也顺应潮流 已经或将要引入闭包支持 相信闭包会成为更多人爱不释手的工具 闭包起源于函数语言 也许掌握一门函数语言是理解闭包的最佳途径 而且通过学习函数语言可以了解不同的编程思想 有益于写出更好的程序