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

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

提问人:SeanDowney 提问时间:7/1/2009 最后编辑:Andrey BelykhSeanDowney 更新时间:10/14/2019 访问量:238940

问:

我正在检查一些功能,并在网站上遇到了一些看起来很有趣的代码: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 闭包

评论


答:

400赞 Andrew Hare 7/1/2009 #1

这就是 PHP 表达闭包的方式。这根本不是邪恶的,事实上它非常强大和有用。

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

评论

1赞 SeanDowney 7/1/2009
所以它只用于闭包?谢谢你的解释,我不知道匿名函数和闭包之间的区别
153赞 9/4/2012
该关键字还用于命名空间的别名。令人惊讶的是,在 PHP 5.3.0 发布 3 年多后,该语法仍然没有正式记录,这使得闭包成为未记录的功能。该文档甚至混淆了匿名函数和闭包。我在 php.net 上能找到的唯一(测试版和非官方)文档是用于关闭的 RFCusefunction ... useuse ()
2赞 rubo77 12/6/2013
那么函数使用闭包是什么时候在PHP中实现的呢?我猜是在 PHP 5.3 中?它现在以某种方式记录在 PHP 手册中吗?
0赞 Manny Fleurmond 11/16/2016
@Mytskine 好吧,根据文档,匿名函数使用 Closure 类
1赞 CJ Dennis 10/30/2019
Now 也用于将 a 包含在 !usetraitclass
57赞 stefs 7/1/2009 #2

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

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

有比上述更好的真实世界示例。假设您必须按子值对多维数组进行排序,但键会发生变化。

<?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。其中一个问题是范围和浏览器固有的异步性。特别是,如果涉及到(或-interval)。所以,你把一个函数传递给setTimeout,但你不能真正给出任何参数,因为提供参数会执行代码!window.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 和匿名函数/闭包。命名空间可能更重要,但它们不那么性感

评论

5赞 SeanDowney 7/2/2009
哦,所以 Uses 是用来传递额外变量的,我认为这是一些有趣的任务。谢谢!
43赞 stefs 7/2/2009
小心。参数用于在调用函数时传递值。闭包用于在定义函数时“传递”值。
1赞 Sᴀᴍ Onᴇᴌᴀ 8/19/2017
在 Javascript 中,可以使用 bind() 来指定函数的初始参数 - 请参阅部分应用的函数
563赞 zupa 4/25/2012 #3

一个更简单的答案。

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

  1. 闭包是分配给变量的函数,因此您可以传递它
  2. 闭包是一个单独的命名空间,通常,您不能访问在此命名空间之外定义的变量。来了 use 关键字:
  3. use 允许您访问(使用)闭包内的后续变量。
  4. 使用是早期绑定。这意味着变量值在定义闭包时被复制。因此,在闭包内部进行修改没有外部效果,除非它是一个指针,就像对象一样。$tax
  5. 您可以将变量作为指针传入,例如 。这样,修改 DOES 的值会产生外部效应,原始变量的值会发生变化。&$total$total
  6. 在闭包内部定义的变量也无法从闭包外部访问。
  7. 闭包和函数具有相同的速度。是的,您可以在脚本中全部使用它们。

正如@Mytskine所指出的,最好的深入解释可能是闭包的RFC。(为此为他投赞成票。

评论

4赞 Kal Zekdor 7/30/2014
use 语句中的 as 关键字在 php 5.5 中给了我一个语法错误: 给出的错误是:$closure = function ($value) use ($localVar as $alias) { //stuff};Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
1赞 zupa 7/30/2014
@KalZekdor,也得到了php5.3的确认,似乎已被弃用。我更新了答案,谢谢你的努力。
4赞 But those new buttons though.. 11/19/2015
我会在点 #5 中补充一点,这样,修改指针的值也会产生内部影响。换言之,如果在定义闭包外部后更改该值,则仅当新值是指针时才会传入该值。&$total$total
2赞 BlackPearl 4/10/2018
这条线停止了我两个小时徒劳的搜索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.
0赞 Devs love ZenUML 1/13/2022
请向下滚动并查看此答案: stackoverflow.com/a/30547499/529187
19赞 joronimo 3/17/2015 #4

Zupa 很好地解释了带有“use”的闭包以及 EarlyBinding 和 Referencing the variable that are used.

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

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

?>
142赞 Steely Wing 5/30/2015 #5

这就像PHP的闭包。function () use () {}

如果没有 ,函数无法访问父作用域变量use

$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

use变量按引用&

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

$s = "how are you?";
$f(); // how are you?
3赞 Zhu Jinxuan 7/10/2019 #6

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

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

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