在匿名函數出現之前,所有的函數都需要先命名才能使用
function increment($value) { return $value + 1; } array_map('increment', [1, 2, 3]);
有的時候函數可能只需要使用一次,這時候使用匿名函數會使得程式碼更加簡潔直觀,同時也避免了函數在其他地方被使用
array_map(function($value){ return $value + 1; }, [1, 2, 3]);
定義和使用
PHP 將閉包和匿名函數視為同等概念(本文統稱為匿名函數),本質上都是偽裝成函數的物件。
匿名函數的本質是物件,因此跟物件一樣可將匿名函數賦值給某一變數
$greet = function(string $name){ echo "hello {$name}"; } $greet("jack") // hello jack
所有的匿名函數都是 Closure 物件的範例
$greet instanceof Closure // true
物件並沒有什麼父作用域可言,所以需要使用 use 來手動宣告使用的變數,
$num = 1; $func = function() use($num){ $num = $num + 1; echo $num; } $func(); // 2 echo $num; // 還是 1
如果要讓匿名函數中的變數生效,需要使用參照傳值
$num = 1; $func = function() use(&$num){ $num = $num + 1; echo $num; } $func(); // 2 echo $num; // 2
從 PHP 5.4 開始,在類裡面使用匿名函數時,匿名函數的 $this 將自動系結到當前類
class Foo { public function bar() { return function() { return $this; }; } } $foo = new Foo(); $obj = $foo->bar(); // Closure() $obj(); // Foo
如果不想讓自動系結生效,可使用靜態匿名函數
class Foo { public function bar() { return static function() { return $this; }; } } $foo = new Foo(); $obj = $foo->bar(); // Closure() $obj(); // Using $this when not in object context
匿名函數的本質
匿名函數的本質是 Closure 物件,包括了以下五個方法
Closure { private __construct ( void ) public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure public call ( object $newthis [, mixed $... ] ) : mixed public static fromCallable ( callable $callable ) : Closure }
__construct - 防止匿名函數被範例化
$closure = new Closure(); // PHP Error: Instantiation of 'Closure' is not allowed
Closure::bindTo - 複製當前匿名函數物件,係結指定的 $this 物件和類作用域。通俗的說,就是手動將匿名函數與指定物件系結,利用這點,可以為擴充套件物件的功能。
// 定義商品類 class Good { private $price; public function __construct(float $price) { $this->price = $price; } } // 定義一個匿名函數,計算商品的促銷價 $addDiscount = function(float $discount = 0.8){ return $this->price * $discount; } $good = new Good(100); // 將匿名函數系結到 $good 範例,同時指定作用域為 Good $count = $addDiscount->bindTo($good, Good::class); $count(); // 80 // 將匿名函數系結到 $good 範例,但是不指定作用域,將無法存取 $good 的私有屬性 $count = $addDiscount->bindTo($good); $count(); // 報錯
Closure::bind - bindTo 方法的靜態版本,有兩種用法:
用法一:實現與 bindTo 方法同樣的效果
$count = Closure::bind($addDiscount, $good, Good::class);
用法二:將匿名函數與類(而不是物件)係結,記得要將第二個引數設定為 null
// 商品庫存為 10 class Good { static $num = 10; } // 每次銷售後返回當前庫存 $sell = static function() { return"當前庫存為". --static::$num ; }; // 將靜態匿名函數系結到 Good 類中 $sold = Closure::bind($sell, null, Good::class); $sold(); // 當前庫存為 9 $sold(); // 當前庫存為 8
call - PHP 7 新增的 call 方法可以實現系結並呼叫匿名函數,除了語法更加簡潔外,效能也更高
// call 版本 $addDiscount->call($good, 0.5); // 係結並傳入引數 0.5,結果為 50 // bindTo 版本 $count = $addDiscount->bindTo($good, Good::class, 0.5); $count(); // 50
fromCallable - 將給定的 callable 函數轉化成匿名函數
class Good { private $price; public function __construct(float $price) { $this->price = $price; } } function addDiscount(float $discount = 0.8){ return $this->price * $discount; } $closure = Closure::fromCallable('addDiscount'); $good = new Good(100); $count = $closure->bindTo($good); $count = $closure->bindTo($good, Good::class); // 報錯,不能重複系結作用域 $count(); // 報錯,無法存取私有屬性
fromCallable 等價於
$reflexion = new ReflectionFunction('addDiscount'); $closure = $reflexion->getClosure();
這裡有一點需要特別注意的是,無論是 fromCallable 轉化成的閉包,還是使用反射得到的閉包,在使用 bindTo 時,如果第二個引數指定系結類,會報錯
Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure()
以上就是PHP 核心特性之匿名函數的詳細內容,更多請關注TW511.COM其它相關文章!