闭包(Closure)是我们在编程中经常遇到的一个概念,但闭包究竟是什么?又有什么用呢?
在编程中,闭包与匿名函数(又名lambda表达式)其实是不同的概念,但两者经常同时使用。在 PHP 中,二者的概念不做区分,下文均以闭包指代。
I. 闭包是什么?
闭包的常用类型
闭包又名匿名函数,也就是没有函数名称的函数。我们先来看下常用类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| $closure = function () { return 7; }
$closure();
(function () { echo 7; })();
function testClosure (Closure $callback) { return $callback(); }
testClosure($closure);
testClosure (function () { return 7; });
function getClosure () { return function () { return 7; }; }
$c = getClosure(); $c();
|
闭包类
定义一个闭包函数,其实是产生了一个闭包类(Closure)的对象,Closure 类摘要如下:
1 2 3 4 5 6 7 8 9 10
| Closure { __construct ( void )
public static bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] ) : Closure public bindTo ( object $newthis [, mixed $newscope = 'static' ] ) : Closure }
|
我们可以通过var_dump($c instanceof Closure);
看到,闭包确实是Closure
的一个实例,通过var_dump(is_callable($c));
看到,闭包是Callable
的数据类型。
Closure::bind()
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class Animal { private static $cat = "cat"; private $dog = "dog"; public $pig = "pig"; }
$cat = static function() { return Animal::$cat; };
$dog = function() { return $this->dog; };
$pig = function() { return $this->pig; };
$bindCat = Closure::bind($cat, null, new Animal());
$bindCat = Closure::bind($cat, new Animal(), 'Animal');
$bindDog = Closure::bind($dog, new Animal(), 'Animal');
$bindPig = Closure::bind($pig, new Animal());
echo $bindCat(),PHP_EOL;
echo $bindDog(),PHP_EOL;
echo $bindPig(),PHP_EOL;
$bindCat = $cat->bindTo(null, 'Animal'); echo $bindCat();
|
如果未能指定作用域的范围,绑定后的闭包只能访问public
属性的值。
Closure::bindTo()
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class A { function __construct($val) { $this->val = $val; }
function getClosure() { return function() { return $this->val; }; } }
$ob1 = new A(1); $ob2 = new A(2);
$cl = $ob1->getClosure(); echo $cl(), PHP_EOL; $cl = $cl->bindTo($ob2); echo $cl(), PHP_EOL;
|
II. 闭包有什么用?
1. 储存变量
1 2 3 4 5 6 7 8 9 10 11
| function makeHelloWorld($name) { $i = 0; return function() use ($name, &$i) { echo $name; $i++; echo $i."\n"; }; } $hello1 = makeHelloWorld("wuying"); $hello2 = makeHelloWorld("wuying1"); $hello1(); $hello1(); $hello1(); $hello2();
|
在这个示例中,$hello1 = makeHelloWorld("wuying");
的作用是将参数wuying1
传递给makeHelloWorld()
,并接收其中的闭包作为返回值,因此,$hello1
便成为了闭包,当我们执行$hello1()
时,闭包开始执行。由于引用了闭包外的变量$i
,所以每次的执行都修改了$i
,所以最后依次输出了wuying1
、wuying2
、wuying3
。
那么,为什么闭包可以存储变量呢?这是因为在没有闭包的语言中,变量的生命周期只限于创建它的环境。但在有闭包的语言中,只要有一个闭包引用了这个变量,它就会一直存在。
另一种在局部函数中储存变量的方式是使用static
关键字,其原理是将变量存储于静态存储区,而非普通变量存储的栈区,详见另一篇文章浅谈PHP中的static关键字。
2. 延时执行函数
比如,我们在 JavaScript 中每隔 1 秒钟输出一个随机数
1
| setInterval(function(){console.log(Math.random())}, 1000)
|
在这个例子中,我们对闭包进行了多次调用,而且不是调用时即时执行。
3. 对处理逻辑封装,形成更好的一体封装
通常我们在调用函数时,传入的是参数是数据,那么只能通过参数对函数的结果进行控制,无法控制其过程,而匿名函数的存在可以作为参数传给函数,也可以作为变量赋值,进而控制函数的执行过程,因此,匿名函数的引入增强了程序编写的灵活性,可以实现更加高效的设计方案。
如果在有闭包前,我们需要单独创建具名函数,然后使用名称引用这个函数
1 2 3 4 5 6
| <?php $func = function($value) { return $value * 2; };
print_r(array_map($func, range(1, 5)));
|
但这样把回调与使用分离,而闭包的出现就很好的解决了这个问题。
而由于闭包的封装性,闭包内无法访问包外变量,但我们可以通过use
关键字来引入外部变量,如:
1 2 3 4
| $quantity = 10; function ($price) use ($quantity) { return $price * $quantity; }
|
再来一个例子,使用闭包打印斐波那契数列。我们知道斐波那契数列有下面的规律:
1 2 3 4
| f(n) = 0; (n = 0) f(n) = 1; (n = 1) f(n) = f(n-1)+f(n-2); (n >= 2)
|
使用递归的方法
1 2 3 4 5 6 7 8 9
| <?php function fibonacci(int $n): int { if ($n < 2) { return $n; } return fibonacci($n-1) + fibonacci($n-2); }
echo fibonacci(10), "\n";
|
如果打印数列,那每一次都需要重复计算前面已经计算过的数据(只需要前两个就好)。可以使用闭包保存上一次的运行环境。
php 版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php $fibonacci = function (): callable { $x = 0; $y = 1; return function () use (&$x, &$y): int { list($x, $y) = [$y, $x+$y]; return $x; }; };
$f = $fibonacci();
for ($i = 0; $i < 10; $i++) { echo $f() , "\n"; }
|
js 版本
1 2 3 4 5 6 7 8 9 10 11 12 13
| let fibonacci = _ => { let x = 0, y = 1; return _ => { [x, y] = [y, x+y]; return x; }; };
let f = fibonacci();
for (let i = 0; i <= 10; i++) { console.log(f()); }
|
总结
合理使用闭包,能够让我们的代码更加清晰、更加灵活。
参考资料
- 闭包和匿名函数 - 学院君
- 闭包的编程思想 - B站
- 闭包-维基百科
- Closure类 - PHP手册
- Callable - PHP手册
- PHP 闭包(Closure)- LearKu
- 学习一下闭包函数 - LearKu