在匿名函數出現以前,全部的函數都須要先命名才能使用php
1 function increment($value) 2 { 3 return $value + 1; 4 } 5 6 array_map('increment', [1, 2, 3]);
有的時候函數可能只須要使用一次,這時候使用匿名函數會使得代碼更加簡潔直觀,同時也避免了函數在其餘地方被使用
1 array_map(function($value){ 2 return $value + 1; 3 }, [1, 2, 3]);
PHP 將閉包和匿名函數視爲同等概念(本文統稱爲匿名函數),本質上都是假裝成函數的對象。閉包
匿名函數的本質是對象,所以跟對象同樣可將匿名函數賦值給某一變量函數
1 $greet = function(string $name){ 2 echo "hello {$name}"; 3 } 4 5 $greet("jack") // hello jack
全部的匿名函數都是 Closure
對象的實例性能
$greet instanceof Closure // true
對象並無什麼父做用域可言,因此須要使用 use
來手動聲明使用的變量,flex
1 $num = 1; 2 $func = function() use($num){ 3 $num = $num + 1; 4 echo $num; 5 } 6 $func(); // 2 7 echo $num; // 仍是 1
若是要讓匿名函數中的變量生效,須要使用引用傳值this
1 $num = 1; 2 $func = function() use(&$num){ 3 $num = $num + 1; 4 echo $num; 5 } 6 $func(); // 2 7 echo $num; // 2
從 PHP 5.4 開始,在類裏面使用匿名函數時,匿名函數的 $this
將自動綁定到當前類spa
1 class Foo { 2 public function bar() 3 { 4 return function() { 5 return $this; 6 }; 7 } 8 } 9 10 $foo = new Foo(); 11 $obj = $foo->bar(); // Closure() 12 $obj(); // Foo
若是不想讓自動綁定生效,可以使用靜態匿名函數code
1 class Foo { 2 public function bar() 3 { 4 return static function() { 5 return $this; 6 }; 7 } 8 } 9 $foo = new Foo(); 10 $obj = $foo->bar(); // Closure() 11 $obj(); // Using $this when not in object context
匿名函數的本質是 Closure
對象,包括瞭如下五個方法orm
1 Closure { 2 private __construct ( void ) 3 public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure 4 public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure 5 public call ( object $newthis [, mixed $... ] ) : mixed 6 public static fromCallable ( callable $callable ) : Closure
}
__construct
- 防止匿名函數被實例化對象
$closure = new \Closure(); // PHP Error: Instantiation of 'Closure' is not allowed
Closure::bindTo
- 複製當前匿名函數對象,綁定指定的 $this
對象和類做用域。通俗的說,就是手動將匿名函數與指定對象綁定,利用這點,能夠擴展對象的功能。
1 // 定義商品類 2 class Good { 3 private $price; 4 5 public function __construct(float $price) 6 { 7 $this->price = $price; 8 } 9 } 10 11 // 定義一個匿名函數,計算商品的促銷價 12 $addDiscount = function(float $discount = 0.8){ 13 return $this->price * $discount; 14 } 15 16 $good = new Good(100); 17 18 // 將匿名函數綁定到 $good 實例,同時指定做用域爲 Good 19 $count = $addDiscount->bindTo($good, Good::class); 20 $count(); // 80 21 22 // 將匿名函數綁定到 $good 實例,可是不指定做用域,將沒法訪問 $good 的私有屬性 23 $count = $addDiscount->bindTo($good); 24 $count(); // 報錯
Closure::bind
- bindTo
方法的靜態版本,有兩種用法:
用法一:實現與 bindTo
方法一樣的效果
$count = \Closure::bind($addDiscount, $good, Good::class);
用法二:將匿名函數與類(而不是對象)綁定,記得要將第二個參數設置爲 null
1 // 商品庫存爲 10 2 class Good { 3 static $num = 10; 4 } 5 6 // 每次銷售後返回當前庫存 7 $sell = static function() { 8 return"當前庫存爲". --static::$num ; 9 }; 10 11 // 將靜態匿名函數綁定到 Good 類中 12 $sold = \Closure::bind($sell, null, Good::class); 13 14 $sold(); // 當前庫存爲 9 15 $sold(); // 當前庫存爲 8
call
- PHP 7 新增的 call
方法能夠實現綁定並調用匿名函數,除了語法更加簡潔外,性能也更高
1 // call 版本 2 $addDiscount->call($good, 0.5); // 綁定並傳入參數 0.5,結果爲 50 3 4 // bindTo 版本 5 $count = $addDiscount->bindTo($good, Good::class); 6 $count(0.5); // 50
fromCallable
- 將給定的 callable
函數轉化成匿名函數
1 class Good { 2 private $price; 3 4 public function __construct(float $price) 5 { 6 $this->price = $price; 7 } 8 } 9 10 function addDiscount(float $discount = 0.8){ 11 return $this->price * $discount; 12 } 13 14 $closure = \Closure::fromCallable('addDiscount'); 15 $good = new Good(100); 16 $count = $closure->bindTo($good); 17 $count = $closure->bindTo($good, Good::class); // 報錯,不能重複綁定做用域 18 $count(); // 報錯,沒法訪問私有屬性
fromCallable
等價於
1 $reflexion = new ReflectionFunction('addDiscount'); 2 $closure = $reflexion->getClosure();
這裏有一點須要特別注意的是,不管是 fromCallable
轉化成的閉包,仍是使用反射獲得的閉包,在使用 bindTo
時,若是第二個參數指定綁定類,會報錯
Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure()