PHP 核心特性 - 匿名函數

 

提出

在匿名函數出現以前,全部的函數都須要先命名才能使用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()
相關文章
相關標籤/搜索