“闭包”这词儿是学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下似乎显得并不那么意义非凡,不过闭包带来的“匿名函数”,也叫做“闭包函数”,却让我们有了实现一些更为灵活程序的基础。我会在介绍匿名函数的一篇文章里,来谈谈关于匿名函数的神奇之处。
https://www.insp.top/article/what-is-closure