一. 什么是閉包?
先看看百度百科的介紹:
閉包包含自由(未綁定到特定對象)變量,這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)柑营。
“閉包” 一詞來源于以下兩者的結合:要執(zhí)行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環(huán)境(作用域)村视。
在PHP官套、Scala、Scheme蚁孔、Common Lisp奶赔、Smalltalk、Groovy杠氢、JavaScript站刑、Ruby、 Python鼻百、Go绞旅、Lua摆尝、objective c、swift 以及Java(Java8及以上)等語言中都能找到對閉包不同程度的支持因悲。
說實話堕汞,這個介紹雖然專業(yè),但是有點僵硬不太容易理解晃琳,閉包是一種設計思想讯检,而不是一種語法特性,在PHP語言里面卫旱,匿名函數(shù)就是閉包的一種實現(xiàn)人灼。
二. 匿名函數(shù)
這個我相信大家都多多少少用過,看一下代碼:
$f = function () {
$a = 1;
$b = 2;
return $a + $b;
};
var_dump($f);
輸出結果是:
class Closure#1 (0) {
}
可見顾翼,PHP中匿名函數(shù)就是閉包投放,也可以理解為閉包就是把這個函數(shù)賦值給一個變量,這時候這個變量保存的就是這個函數(shù)的內存地址适贸。
如何去調用這個閉包函數(shù)呢跪呈?很簡單,在這個例子里面只要 $f() 就可以了
var_dump($f()); #結果是:3
當然這個匿名函數(shù)也是可以傳參的取逾,你可以這樣寫:
$f = function ($c) {
$a = 1;
$b = 2;
return $a + $b + $c;
};
這樣你在調用的時候就可以傳入?yún)?shù)耗绿,類似 $f(3), 但是有一點需要注意,如果這時候你想在定義閉包函數(shù)的時候使用外部變量砾隅,舉個例子
$out = 100;
$f = function ($c) {
$a = 1;
$b = 2;
return $out - ($a + $b + $c); #報錯误阻,無法引用外部變量out
};
這時候就體現(xiàn)了閉包封閉的特性,但是PHP提供了一個 use 關鍵字晴埂,可以使用下面這個寫法:
$out = 100;
$f = function ($c) use ($out) {
$a = 1;
$b = 2;
return $out - ($a + $b + $c);
};
三. 閉包到底有啥用究反?
一般來說還是在框架以及一些架構設計里面會用到,這里先舉2個小例子:
$arr = [1, 2, 3, 4, 5];
//使用array_reduce求和
function sum($arr)
{
return array_reduce($arr, function ($x, $y) {
return $x + $y;
});
}
var_dump(sum($arr));
代碼里面使用了 array_reduce 這個函數(shù)求一個數(shù)組的和儒洛,但是精耐,如果不需要立刻求和,而是在后面的代碼中琅锻,根據(jù)需要再計算怎么辦卦停?可以不返回求和的結果,而是返回求和的函數(shù)恼蓬!
function lazySum($arr)
{
return function () use ($arr) {
return array_reduce($arr, function ($x, $y) {
return $x + $y;
});
};
}
$sum = lazySum($arr);
var_dump($sum());
結果是一樣的惊完,雖然這種寫法有點奇怪
有一道面試題就涉及到了閉包的特性:
function plus()
{
$funcArr = [];
for ($i = 1; $i <= 3; $i++) {
$funcArr[] = function () use (&$i) {
return $i * $i;
};
}
return $funcArr;
}
$res = plus();
var_dump($res[0]());
var_dump($res[1]());
var_dump($res[2]());
plus 函數(shù)會返回3個閉包函數(shù),然后依次調用這個幾個函數(shù), 有人以為結果可能是1,4,9处硬,其實結果都是16小槐,需要注意的是use 那里使用的是引用傳遞,這就意味著在生成這3個閉包函數(shù)的時候i的值并不是循環(huán)的時候的1,2,3荷辕,而且到最后 $i++ 之后的值也就是 4凿跳,這說明閉包函數(shù)是調用的時候才會執(zhí)行件豌。
四. 閉包在PHP框架里面使用
1.一個是IOC容器
<?php
/**
* 閉包的使用IOC
* Class Container
*/
class Container
{
protected static $bindings;
public static function bind(string $abstract, Closure $concrete)
{
static::$bindings[$abstract] = $concrete;
}
public static function make(string $abstract)
{
return call_user_func(static::$bindings[$abstract]);
}
}
class talk
{
public function greet($target)
{
echo "Hello " . $target->getName();
}
}
class say
{
public function getName()
{
return "World\n";
}
}
$talk = new talk();
Container::bind('foo', function () {
return new say();
});
$talk->greet(Container::make('foo'));
接觸過laravel框架的應該都見過這種寫法,laravel框架稱之為服務容器控嗜,其設計思想基本上就是這樣苟径,也就是在框架初始化的時候注冊綁定一堆服務,然后框架里面隨時就可以調用這些服務了躬审。
2.閉包路由
/**
* 演示閉包的使用,路由
* Class App
*/
class App
{
protected $routes = [];
protected $responseStatus = '200 OK';
protected $responseContentType = 'text/html';
protected $responseBody = 'Hello World';
public function addRoute(string $path, Closure $callback)
{
$this->routes[$path] = $callback->bindTo($this, __CLASS__);
}
public function dispatch(string $path)
{
foreach ($this->routes as $routePath => $callback) {
if ($routePath === $path) {
$callback();
}
}
header('HTTP/1.1 ' . $this->responseStatus);
header('Content-Type: ' . $this->responseContentType);
header('Content-Length: ' . mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
require 'App.php';
$app = new App();
$app->addRoute("/", function () {
$this->responseBody = "Hello Closure!\n";
});
$app->dispatch("/");