關於一篇文章引發的匿名函式的思考

三木閒僧發表於2019-06-12

寫在前面

閒來無事在瀏覽Laravel 底層分析這篇文章的時候,發現自己對匿名函式只是停留在表面上的理解,然鵝,作為一名“優秀”的?‍?‍,發自內心的好奇心讓我去好好把玩了下匿名函式這個東西。

匿名函式

匿名函式(Anonymous functions),也叫閉包函式(closures),允許 臨時建立一個沒有指定名稱的函式。最經常用作回撥函式(callback)引數的值,也可以作為函式的返回值。

示例1:

//宣告一個匿名函式,並呼叫
<?php
$say = function() {
    echo 'hello';    //宣告一個匿名函式,並賦值給$say這個變數,注意函式宣告後面的‘;’
};
$say();    //變數加上()就可以呼叫匿名函式,輸出“hello”

示例2:

//宣告一個匿名函式,並作為回撥函式(callback)引數的值
$say = function($name) {
    echo 'hello '.$name;
};
call_user_func($say,'dog');    //call_user_func呼叫引數1的回撥函式,並將引數2當成回撥函式的引數,輸出“hello dog” 

匿名函式 和 閉包類(Closure)的關係

定義一個閉包函式,會產生一個閉包類(Closure)的物件。

Closure 類主要方法:

Closure {
/* 方法 */
    __construct ( void )
    public static bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] ) : Closure
    public bindTo ( object $newthis [, mixed $newscope = 'static' ] ) : Closure
}
  • Closure::construct — 用於禁止例項化的建構函式

    這個方法僅用於禁止例項化一個 Closure 類的物件。

  • Closure::bind — 用來複制引數1給定的閉包,並指定閉包的 this 指標指向引數2,閉包的類的作用域為 引數3

    引數

    • closure
      需要繫結的匿名函式。
    • newthis
      需要繫結到匿名函式的物件,或者 NULL 建立未繫結的閉包。
    • newscope
      想要繫結給閉包的類作用域,或者 'static' 表示不改變。如果傳入一個物件,則使用這個物件的型別名。 類作用域用來決定在閉包中 $this 物件的 私有、保護方法 的可見性
  • Closure::bindTo — 複製當前閉包物件,繫結指定的$this物件和類作用域。

    用法和bind差不多,這裡就不多贅述

示例1:

//宣告一個狗物件
class Dog
{
    public $name = '旺財';
}

//宣告一個貓物件
class Cat
{
    public $name = '喵喵';
}

//宣告一個匿名函式,用來輸出名字
$sayName = function() {
    echo $this->name;
};

//用bind賦值上面那個匿名函式,並指定this指向狗物件,返回新的閉包函式
$sayDogName = Closure::bind($sayName, new Dog);

//呼叫新的閉包函式,輸出 “旺財”
$sayDogName();  

//用bind賦值上面那個匿名函式,並指定this指向貓物件,返回新的閉包函式
$sayCatName = Closure::bind($sayName, new Cat);

//呼叫新的閉包函式,輸出 “喵喵”
$sayCatName();

//用bind賦值上面那個匿名函式,this指向null
$sayNullName = Closure::bind($sayName, null);

//呼叫新的閉包函式,由於this指向null,報錯 “Using $this when not in object context”
$sayNullName();

上面這個例子闡明瞭引數2的作用,那麼引數3呢,往下瞅

示例2:

//還是宣告一條狗狗,不過狗狗的名字變成了私有,只有主人才知道狗狗叫旺財
class Dog
{
    private $name = '旺財';
}

//宣告匿名函式
$sayName = function() {
    echo $this->name;
};

//用bind賦值上面那個匿名函式,並指定this指向狗物件,返回新的閉包函式
$sayDogName = Closure::bind($sayName, new Dog);

//呼叫新的閉包函式,報錯 “Cannot access private property Dog::$name”
$sayDogName();  

原因:
我們將 this 指向了 Dog 物件,但是我們並沒有把類的作用域指向 Dog,所以,閉包函式 sayDogName 無法呼叫 Dog 的私有屬性 name。接下來,我們來指定下類的作用域。

示例3:

//指定類的作用域為 Dog 物件
$sayDogName = Closure::bind($sayName, new Dog, 'Dog');

//輸出 “旺財”
$sayDogName();  

匿名函式 和 Use

use是用來是連線閉包和外界變數。

示例1:

$a = 1;

$b = function () use($a) {
    $a += 10;
    echo $a.PHP_EOL;    //輸出“11”
};

$b();

echo $a;    //輸出1

那麼如果我們傳遞的變數是個物件呢?

class Dog
{
    public $name = '旺財';
}

$aDog = new Dog;

$closureFunc = function() use($aDog) {
    $aDog->name = '旺財他二爺';
};

$closureFunc();

echo $aDog->name;    //輸出“旺財他二爺”

咦,不是說閉包函式裡面的操作不會改變閉包函式外的變數嗎?咋回事??
其實,物件變數屬於引用型別的資料,use在複製物件變數的時候,雖然操作的是另外一個變數,但是這個變數其指向的物件地址並沒有發生改變,所以當我們對複製的物件變數進行操作的時候,物件的屬性也會發生相應的改變,同時會影響到其他也指向這個物件的變數。

舉個簡單?做下對比:

$a = 1;

$b = $a;

$b = 2;

echo $a;    //輸出1

class Dog
{
    public $name = '旺財';
}

$a = new Dog;

$b = $a;    

$b->name = '旺財的兒子';

echo $a->name;    //輸出“旺財的兒子”

同理,如果我們需要同時改變閉包外普通變數$a的值,那麼我們就需要引用傳值&,如下:

$a = 1;

$b = function () use(&$a) {
    $a += 10;
    echo $a.PHP_EOL;    //輸出“11”
};

$b();

echo $a;    //輸出11

最後關於 Use 傳值的一點小疑問

匿名函式也可以通過函式的引數傳遞變數進來,如下

$a = 1;

$b = function ($a)  {
    $a += 10;
    echo $a.PHP_EOL;    //輸出“11”
};

$b($a);

echo $a;    //輸出1

那麼這種傳值方式和Use有何區別?希望看到的小夥伴可以在評論區幫小弟解答一下,感激不盡!!

相關文章