Inspirer

闭包——藏在代码中的“房间”

“闭包”这词儿是学javascript时听来的。没错,听来的。我一直对这个词所代表的学术概念嗤之以鼻,不为别的,就因为这些概念严重的欺负了我对概念性知识的超弱理解能力。正是如此,让我一直对明确其概念这一行为抱有深深的芥蒂。

不过,哪能跟自己过不去呢?于是,在不断学习新事物的同时,不知不觉中就慢慢领会了这个小家伙的神奇之处。

由于本人写此文时主要偏好于php,主要以php中的闭包作为阐述对象,其他语言的闭包概念与其产生的冲突,若是我了解的,我会补充说明。好吧,还是聊聊闭包——藏在代码中的“房间”。

什么是闭包?

引用一段百度百科的第一句介绍:

闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。

如果你也是第一次了解这玩意儿,像我一样对概念性文字头大的人,懵懵懂懂的去开始走进闭包——一个对你而言是从未了解过概念时,你会由衷地感叹——这TM是啥(四声)JB玩意儿。

这段描述其实没错。只是有点绕。

来说说闭包

闭包的概念往往无法通过描述解释清楚,我就先来写一段代码:

<?php
function foo()
{
    $i = 0;
    $bar = function() use (&$i) {
        return ++$i;
    }
    return $bar;
}

$closure = foo();

echo $closure(); // 1
echo $closure(); // 2
?>

以上代码中,我们可以说$closure就是一个闭包。

上述例子中,我们无法直接从外部获取或者操作函数foo中的局部变量$i。在上述例子中,在函数内部定义了一个没有名字的函数,这个是匿名函数。关于匿名函数可以在另一篇文章里找到介绍。该匿名函数由于在foo函数内,自然而然通过use并且以引用的方式获取其内部变量$i(若是javascript则连use这一关键字都不需要),并对该变量进行自加(++)操作,然后返回。

匿名函数作为一种可被传递的“值”赋给了变量 $bar,并返回。

我们在示例代码第11行,全局变量 $closure 接收了 foo 函数返回的匿名函数,我们通过 $closure() 这一方式调用了这一个匿名函数,由于该匿名函数看似是在外部被调用,但实际上而言,匿名函数在定义的时候引用了它当时所处的上下文的变量 $i,而该匿名函数最终又被赋予了全局变量 $closure,假如全局变量 $closure 不被释放,则 $i 里面的值将会一直保留而不会被 GC(垃圾回收机制)所释放,因此,每一次调用该匿名函数的结果都是在上一次运算结果的基础上累加。

好了,现在我们理一理。

其实,简单理解,闭包就是一个操作函数内部变量的东西,它往往以匿名函数的形式体现,因为“操作”是一个过程、一个逻辑的实现,简单的代码无法完成,而匿名函数内,就和一般函数一样,里面可以包含一个完整的逻辑。因此,匿名函数有时候也叫做闭包函数,他是在一个封闭代码内的一个可以与外界沟通的桥梁,就像一个封闭的军事基地中的一个通讯室一样,一个藏在代码中的“房间”。

闭包的用处

我们看得出,闭包有一个很有用的功能就是保证了内部变量不被释放。这在 javascript 里很有用。

但在 php 里这个用处不像 javascript,为什么?php 里你可以通过 static 将变量声明为静态,在整个程序执行期间,这个静态变量会一直保存在内存中而不会被释放,而 javascript 为了保证一些变量不被释放,只能保持其引用状态,这时候就可以利用闭包。我们把上一个例子中的php代码换成差不多的 javascript 代码:

function foo()
{
    var i = 0;
    var bar = function() {
        return ++i;
    }
    return bar;
}

closure = foo();

alert(closure()); // 1
alert(closure()); // 2

上述代码中,由于将来自foo内部的匿名函数赋予了全局变量closure,因此程序运行期间都将保持对该匿名函数的引用,且该匿名函数引用了foo内部的变量,相当于程序运行期间也必须保持引用内部变量i,因此我们可以看到,i的值得以保留上一次的运算结果。假设我们没有使用闭包去引用这个内部变量i,将代码变为下面这个例子:

function foo()
{
    var i = 0;
    return ++i;
}

alert(foo()); // 1
alert(foo()); // 1

两次输出的值都为1,说明变量i在每次自加后,由于没有被其他地方所引用因而被释放,最终导致了两次得到的都是初始化后的i自加的结果。所以说,在 javascript 中,这样做的意义非常大,可以更灵活的实现更多功能。当然,php程序也可以这样,只是我们有其他的替代方案而已。不过php要通过匿名函数引用内部变量需要使用use,而且引用传值要求变量名前面必须要加&,这是和javascript不一样的地方。

对于php而言,匿名函数的作用远远大于闭包,虽然两者关系紧密,要知道,闭包通常只能以匿名函数的方式实现,这也是为什么很多人会将两者概念搞混淆的原因。

还可以做什么

有时候,我们太过于计较一个设计能做什么的时候,往往带来太多困惑,其实存在即合理,有时候只是没发现,也许在某一天,某个项目的开发遇到头疼的问题时,这些特性说不准会让你突然脑洞大开。当然,闭包的用处太多了,尤其是 javascript 的开发。

闭包的用处在php下似乎显得并不那么意义非凡,不过闭包带来的“匿名函数”,也叫做“闭包函数”,却让我们有了实现一些更为灵活程序的基础。我会在介绍匿名函数的一篇文章里,来谈谈关于匿名函数的神奇之处。