閉包匿名函式,還在傻傻搞不清楚嗎?

悲劇不上演發表於2019-02-14

還在正月裡,首先祝大家新年快樂!本文是從PHP角度來看閉包與匿名函式。

閉包基礎

閉包長啥樣

以下是最常見的閉包形式,其他方法建立的閉包在此就不做說明了


$helloWorld = function() {
    return 'hello world';
};

var_dump($helloWorld);

/*
返回結果如下:
object(Closure)#1 (0) {
}
*/

定義閉包

閉包 = 匿名函式 = 例項 = 物件。 類程式碼大概如下:


// 閉包類
class Closure{
    // 禁止例項化
    private __construct()
    {

    }

    // 複製一個閉包,繫結指定的 $newThis 物件和類的作用域
    public static function bind(Closure $closure, object $newThis, mixed $newscope = 'static') :Closure
    {
        // TO-DO
    }

    // 複製當前閉包物件, 繫結指定的 $newThis 物件和類的作用域 
    public function bindTo(object $newThis, mixed $newscope = 'static') :Closure
    {
        // TO-DO
    }

    // 魔術方法,無作用
    public function __invoke()
    {
        // TO-DO
    }
}
方法 說明
__construct 用於禁止例項化的構造方法
bind 複製一個閉包,繫結指定的$this物件和類作用域
bindTo 複製當前閉包,繫結指定的$this物件和類作用域

真實應用閉包

例如我們程式碼中需要將陣列值轉化int型別

$params = ['10', '20', '30', '40'];

$newArray = array_map(function($item) {
    return intval($item);
}, $params);

var_dump($newArray);

/*
返回結果如下:
array(4) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  int(30)
  [3]=>
  int(40)
}

*/

閉包高階應用

Closure::bind

通俗點理解就是讓我們的閉包程式碼塊的作用域在某一類中或者物件中。使得閉包中的 $this-> self:: 類名::能指定到物件或者是類

程式碼塊


/**
 * Closure class a method
 * 複製一個閉包,繫結指定的 $newThis物件和類的作用域
 *
 * @param Closure $closure
 * @param object $newThis
 * @param mixed $newscope
 * @return \Closure
 */
public static function bind(Closure $closure, object $newThis, mixed $newscope = 'static') :Closure
{
    // TO-DO
}

引數說明

PHP 手冊 中有以下幾句可加深我們理解:

建立並返回一個 匿名函式, 它與當前物件的函式體相同、繫結了同樣變數,但可以繫結不同的物件,也可以繫結新的類作用域。

“繫結的物件”決定了函式體中的 $this 的取值,“類作用域”代表一個型別、決定在這個匿名函式中能夠呼叫哪些 私有 和 保護 的方法。 也就是說,此時 $this 可以呼叫的方法,與 newscope 類的成員函式是相同的。

靜態閉包不能有繫結的物件( newthis 引數的值應該設為 NULL)不過仍然可以用 bubdTo 方法來改變它們的類作用域。


引數 說明
$closure 表示閉包函式
$newThis 閉包中 $this 所指的物件
$newscope 我們閉包中需要操作屬性等所屬類的型別名

程式碼檢驗真理

class Order
{
    private $orderId = '001';

    private static $defaultMoney = '100';

    public $num = 1;
}

$getOrderId = function() {
    return $this->orderId;
};

$getNum = function() {
    return $this->num;
};

$getDefaultMoney = static function() {
    return Order::$defaultMoney;
};
1. Closure::bind($closure,null, 'Order')

給閉包繫結了Order類的作用域,但未繫結閉包$this物件 (Order::class == new Order() == 'Order')

$getDefaultMoney1 = Closure::bind($getDefaultMoney, null, Order::class);
// 輸出:100

$getOrderId1 = Closure::bind($getOrderId, null, Order::class);
// 輸出:PHP Fatal error:  Uncaught Error: Using $this when not in object context

$getNum1 = Closure::bind($getNum, null, Order::class);
// 輸出:PHP Fatal error:  Uncaught Error: Using $this when not in object context
2. Closure::bind($closure,$object, 'Order')

給閉包繫結了Order類的作用域,將Order例項繫結閉包$this物件

$getDefaultMoney2 = Closure::bind($getDefaultMoney, new Order(), Order::class);
// 輸出:PHP Warning:  Cannot bind an instance to a static closure

$getOrderId2 = Closure::bind($getOrderId, new Order(), Order::class);
// 輸出 string(3) "001"

$getNum2 = Closure::bind($getNum, new Order(), Order::class);
// 輸出 int(1)
3. Closure::bind($closure,$object)

將Order例項物件作為$this物件繫結給閉包,保留閉包原有作用域

$getDefaultMoney3 = Closure::bind($getDefaultMoney, new Order());
// 輸出 PHP Warning:  Cannot bind an instance to a static closure

$getOrderId3 = Closure::bind($getOrderId, new Order());
// 輸出 Fatal error: Uncaught Error: Cannot access private property Order::$orderId

$getNum3 = Closure::bind($getNum, new Order());
// 輸出 int(1)

結論

$newThis $newscope 結果
null Order::class 可呼叫 Order 類作用域私有(受保護)的靜態屬性
new Order() Order::class 可呼叫該物件的私有(受保護)的屬性
new Order() 預設值 可呼叫類作用域公共的屬性

$closure->bindTo

Closure::bind相同。一個是靜態版,一個是非靜態版。具體使用可以參考上面

$getOrderId1 = $getOrderId->bindTo(new Order(), Order::class);

小任務

利用閉包在不修改Order類的前提下增加 getOrderId 功能。最後實現的是如下兩行程式碼呼叫方法。大家可以思考下

$order = new Order();
echo $order->getOrderId();

// 程式碼段
function getOrderId()
{
    return $this->orderId;
}

閉包 == 匿名函式

感謝@Wi1dcard補充: 在 PHP 內,由於匿名函式是通過閉包類實現的( Anonymous functions are implemented using the Closure class),因此多數人混淆了閉包技術和匿名函式;實際上,在其他語言內是完全不同的兩個概念的。

參考:
http://php.net/manual/en/functions.anonymo...
https://stackoverflow.com/questions/491211...

閉包是一項「技術」或者說「功能」,能夠捕獲並儲存當前當前上下文狀態,以供後續使用。
匿名函式就只是一個「函式」,一個沒有名字的函式而已。
在實際應用中,匿名函式通常伴隨著使用閉包技術;但閉包並不一定只能用在匿名函式內。

相關連結

相關文章