我正在检查一些PHP 5.3.0的特性,在网站上看到了一些看起来很有趣的代码:

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);
}

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

有人知道吗?文档吗?它看起来很邪恶,应该被使用吗?


这就是PHP表达闭包的方式。这一点都不邪恶,事实上它是相当强大和有用的。

基本上,这意味着您允许匿名函数在其作用域之外“捕获”局部变量(在本例中为$tax和对$total的引用),并将它们的值(或在$total的情况下为对$total本身的引用)保存为匿名函数本身的状态。


闭包很漂亮!它们解决了许多匿名函数带来的问题,并使真正优雅的代码成为可能(至少只要我们谈论php)。

Javascript程序员一直在使用闭包,有时甚至不知道它,因为绑定变量没有显式定义——这就是php中“use”的含义。

现实世界中有比上面的例子更好的例子。假设你必须按子值对多维数组排序,但是键值改变了。

<?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,但你不能给出任何参数,因为提供参数会执行代码!

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和匿名函数/闭包。名称空间可能更重要,但它们没有那么吸引人。


一个更简单的答案。

功能($quantity)使用($tax, &$total) {..};

The closure is a function assigned to a variable, so you can pass it around A closure is a separate namespace, normally, you can not access variables defined outside of this namespace. There comes the use keyword: use allows you to access (use) the succeeding variables inside the closure. use is early binding. That means the variable values are COPIED upon DEFINING the closure. So modifying $tax inside the closure has no external effect, unless it is a pointer, like an object is. You can pass in variables as pointers like in case of &$total. This way, modifying the value of $total DOES HAVE an external effect, the original variable's value changes. Variables defined inside the closure are not accessible from outside the closure either. Closures and functions have the same speed. Yes, you can use them all over your scripts.

正如@Mytskine指出的,可能最好的深入解释是闭包的RFC。(给他点赞。)


Zupa很好地解释了带“use”的闭包,以及EarlyBinding和引用“used”变量之间的区别。

所以我做了一个早期绑定变量(=复制)的代码示例:

<?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
*/

?>

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

<?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
*/

?>

函数()use(){}类似于PHP的闭包。

如果不使用,函数不能访问父作用域变量

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

use变量的值是函数定义时的值,而不是调用时的值

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$s = "how are you?";
$f(); // hello

使用变量引用&

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?

直到最近几年,PHP已经定义了它的AST, PHP解释器已经将解析器与求值部分隔离开来。在引入闭包期间,PHP解析器与计算高度耦合。

因此,当闭包第一次引入PHP时,解释器没有方法知道闭包中将使用哪些变量,因为它还没有被解析。所以用户必须通过显式导入zend引擎,做zend应该做的功课。

这就是PHP中所谓的简单方法。