在PHP中,什么是闭包,为什么它使用“use”标识符?

In PHP, what is a closure and why does it use the “use” identifier?

我正在检查一些PHP 5.3.0功能,并在站点上运行了一些看起来很有趣的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function getTotal($tax)
{
    $total = 0.00;

    $callback =
        /* This line here: */
        function ($quantity, $product) use ($tax, &$total)
        {
            $pricePerItem = constant(__CLASS__ ."::PRICE_" .
                strtoupper($product));
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };

    array_walk($this->products, $callback);
    return round($total, 2);
}

作为匿名函数的一个例子。

有人知道吗?有文件吗?它看起来很邪恶,应该用过吗?


一个简单的答案。

function ($quantity) use ($tax, &$total) { .. };

  • 闭包是一个分配给变量的函数,因此可以传递它
  • 闭包是一个单独的命名空间,通常情况下,不能访问在此命名空间之外定义的变量。出现了use关键字:
  • 使用允许您访问(使用)闭包内的后续变量。
  • 使用是早期绑定。这意味着在定义闭包时复制变量值。因此,在闭包内部修改$tax没有外部效果,除非它是一个指针,就像一个对象一样。
  • 您可以像在&$total的情况下那样将变量作为指针传入。这样,修改$total的值确实会产生外部影响,原始变量的值会发生变化。
  • 在闭包内部定义的变量也不能从闭包外部访问。
  • 闭包和函数具有相同的速度。是的,您可以在整个脚本中使用它们。
  • 正如@mytskine指出的,最好的深入解释可能是闭包的RFC。(向他投赞成票。)


    这就是PHP表示闭包的方式。这一点也不邪恶,事实上,它是非常强大和有用的。

    基本上,这意味着您允许匿名函数"捕获"它范围之外的局部变量(在本例中,是$tax和对$total的引用),并将它们的值(或在$total的引用中,是对$total本身的引用)保留为匿名函数本身的状态。


    封口很漂亮!它们解决了匿名函数带来的许多问题,并使真正优雅的代码成为可能(至少在我们讨论PHP的时候)。

    JavaScript程序员总是使用闭包,有时甚至不知道闭包,因为绑定变量没有被明确定义——这就是"use"在PHP中的用途。

    有比上面的例子更好的真实世界的例子。假设必须按子值对多维数组进行排序,但键会更改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php
        function generateComparisonFunctionForKey($key) {
            return function ($left, $right) use ($key) {
                if ($left[$key] == $right[$key])
                    return 0;
                else
                    return ($left[$key] < $right[$key]) ? -1 : 1;
            };
        }

        $myArray = array(
            array('name' => 'Alex', 'age' => 70),
            array('name' => 'Enrico', 'age' => 25)
        );

        $sortByName = generateComparisonFunctionForKey('name');
        $sortByAge  = generateComparisonFunctionForKey('age');

        usort($myArray, $sortByName);

        usort($myArray, $sortByAge);
    ?>

    警告:未测试的代码(我没有安装php5.3 atm),但它看起来应该是这样的。

    有一个缺点:如果你用闭包来对付他们,很多PHP开发人员可能会有点束手无策。

    为了更好地理解闭包的好处,我将给出另一个例子——这次是在JavaScript中。其中一个问题是范围界定和浏览器固有的异步性。尤其是当涉及到window.setTimeout();(或区间)。所以,您将一个函数传递给setTimeout,但实际上不能提供任何参数,因为提供参数会执行代码!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function getFunctionTextInASecond(value) {
        return function () {
            document.getElementsByName('body')[0].innerHTML = value; //"value" is the bound variable!
        }
    }

    var textToDisplay = prompt('text to show in a second', 'foo bar');

    // this returns a function that sets the bodys innerHTML to the prompted value
    var myFunction = getFunctionTextInASecond(textToDisplay);

    window.setTimeout(myFunction, 1000);

    MyFunction返回一个带有预定义参数的函数!

    老实说,自从5.3和匿名函数/闭包以来,我更喜欢PHP。名称空间可能更重要,但它们不那么性感。


    function () use () {}是PHP的闭包,必须使用use来包含父级function的变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <?php
    $message ="hello
    "
    ;


    $example = function () {
        echo $message;
    };
    // Notice: Undefined variable: message
    $example();


    $example = function () use ($message) {
        echo $message;
    };
    //"hello"
    $example();


    // Inherited variable's value is from when the function is defined, not when called
    $message ="world
    "
    ;
    //"hello"
    $example();


    // Inherit by-reference
    $message ="hello
    "
    ;
    $example = function () use (&$message) {
        echo $message;
    };
    //"hello"
    $example();
    // The changed value in the parent scope is reflected inside the function call
    $message ="world
    "
    ;
    //"world"
    $example();


    // Closures can also accept regular arguments
    $example = function ($arg) use ($message) {
        echo $arg . ' ' . $message;
    };
    //"hello world"
    $example("hello");


    zupa用"use"解释了闭包,以及早期绑定和引用"used"变量之间的区别。

    因此,我做了一个代码示例,其中早期绑定了一个变量(=copying):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?php

    $a = 1;
    $b = 2;

    $closureExampleEarlyBinding = function() use ($a, $b){
        $a++;
        $b++;
        echo"Inside \$closureExampleEarlyBinding() \$a =".$a."<br />";
        echo"Inside \$closureExampleEarlyBinding() \$b =".$b."<br />";    
    };

    echo"Before executing \$closureExampleEarlyBinding() \$a =".$a."<br />";
    echo"Before executing \$closureExampleEarlyBinding() \$b =".$b."<br />";  

    $closureExampleEarlyBinding();

    echo"After executing \$closureExampleEarlyBinding() \$a =".$a."<br />";
    echo"After executing \$closureExampleEarlyBinding() \$b =".$b."<br />";

    /* this will output:
    Before executing $closureExampleEarlyBinding() $a = 1
    Before executing $closureExampleEarlyBinding() $b = 2
    Inside $closureExampleEarlyBinding() $a = 2
    Inside $closureExampleEarlyBinding() $b = 3
    After executing $closureExampleEarlyBinding() $a = 1
    After executing $closureExampleEarlyBinding() $b = 2
    */


    ?>

    引用变量的示例(注意变量前的"&;"字符);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?php

    $a = 1;
    $b = 2;

    $closureExampleReferencing = function() use (&$a, &$b){
        $a++;
        $b++;
        echo"Inside \$closureExampleReferencing() \$a =".$a."<br />";
        echo"Inside \$closureExampleReferencing() \$b =".$b."<br />";
    };

    echo"Before executing \$closureExampleReferencing() \$a =".$a."<br />";
    echo"Before executing \$closureExampleReferencing() \$b =".$b."<br />";  

    $closureExampleReferencing();

    echo"After executing \$closureExampleReferencing() \$a =".$a."<br />";
    echo"After executing \$closureExampleReferencing() \$b =".$b."<br />";    

    /* this will output:
    Before executing $closureExampleReferencing() $a = 1
    Before executing $closureExampleReferencing() $b = 2
    Inside $closureExampleReferencing() $a = 2
    Inside $closureExampleReferencing() $b = 3
    After executing $closureExampleReferencing() $a = 2
    After executing $closureExampleReferencing() $b = 3
    */


    ?>