11.5 方法 3:闭包
什么是闭包
说到闭包(closure)这个概念,想必很多人一时也说不出究竟何为闭包。它是创建具有对象性质的事物的一种技术。
很多语言都支持定义带有某种状态的函数。比如,可以定义像计数器一样每调用一次显示的数字加 1 的函数。我们用 JavaScript 语言来实现一下。
JavaScript
function makeCounter(){
var count = 0;
function push(){
count++;
console.log(count);
}
return push;
}
c = makeCounter();
c(); //-> 1
c(); //-> 2
c(); //-> 3
这段代码中,函数 makeCounter 中定义了变量 count 和函数 push,并返回函数 push。然后,通过调用函数 makeCounter 将返回值赋给变量 c,然后调用它三次。每调用一次,显示的值就加 1。这是怎么做到的呢?函数 makeCounter 首先创建了一张名字和值的对照表,把变量 count 的值设为 0。然后定义了函数 push,并将其返回。函数 push 将其被定义时的对照表一同带出 makeCounter 函数。随后,每当被调用时,函数 push 在被定义时的对照表的值就加 1。
事实上,并没有所谓闭包的特殊的语法结构。如果有一种语言 14,它可以在函数中定义函数,有允许嵌套的静态作用域,并且可以把函数作为返回值传递给变量,那么它只要通过函数的嵌套就可以实现带有某种状态的函数。
14大部分的函数式语言和 JavaScript 语言以及 Python 语言都满足这一条件。Perl 语言和 Ruby 语言经过一些处理也可以符合这一要求。
为什么叫做闭包
看到闭包这个名字,总有一种什么东西被严实地包裹起来了的感觉。为什么会叫做闭包呢?某 Standard ML 的教材上做了如下解释。
为什么把这称为闭包?一个包含了自由变量的开放表达式,它和该自由变量的约束环境组合在一起后,实现了一种封闭的状态。
── Åke Wikström, Functional programming using standard ML, Prentice-Hall, 1987.15
15原文是“The reason it is called a‘closure’is that an expression containing free variables is called an‘open’expression, and by associating to it the bindings of its free variables, you close it”。
拿上面一段代码来讲,函数 push 使用了变量 count,然而该变量并不是在函数 push 中定义的。这种变量被称为自由变量。函数 push 就是一个包含了自由变量的开放函数。而函数 makeCounter 的对照表中为 0 的值和为 count 的名字结合在了一起。这种给值绑定一个名字的操作叫做(名字)约束。这样开放函数 push 和 makeCounter 的对照表组合配套之后,无需在这以外的作用域中寻找变量的定义,从而达到了某种完备的状态。通过这样表现出一种封闭的属性。
