【问题标题】:In PHP, what is a closure and why does it use the "use" identifier?在 PHP 中,什么是闭包,为什么它使用“use”标识符?
【发布时间】:2010-11-07 02:27:47
【问题描述】:

我正在检查一些 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);
}

作为anonymous functions 上的示例之一。

有人知道吗?任何文件?而且它看起来很邪恶,应该使用它吗?

【问题讨论】:

    标签: php closures


    【解决方案1】:

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

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

    【讨论】:

    • 所以它只用于闭包?谢谢你的解释,我不知道匿名函数和闭包的区别
    • use 关键字也用于aliasing namespaces。令人惊讶的是,在 PHP 5.3.0 发布 3 年多之后,语法 function ... use 仍然没有正式记录,这使得闭包成为一个未记录的特性。文档甚至confuses anonymous functions and closures。我可以在 php.net 上找到的关于 use () 的唯一(测试版和非官方)文档是 RFC for closures
    • 所以When was function use closures implemented in PHP? 我猜那是在PHP 5.3 中?它现在以某种方式记录在 PHP 手册中了吗?
    • @Mytskine 好吧,根据文档,匿名函数使用 Closure 类
    • 现在use 也用于将trait 包含到class 中!
    【解决方案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 中。问题之一是范围和浏览器固有的异步性。特别是,如果涉及到window.setTimeout();(或-interval)。所以,你给 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。命名空间可能更重要,但它们没有那么性感

    【讨论】:

    • ohhhhhhhh,所以 Uses 是用来传入 extra 变量的,我觉得这是一个有趣的赋值。谢谢!
    • 小心。调用函数时,参数用于传递值。当函数被定义时,闭包用于“传递”值。
    • 在 Javascript 中,可以使用 bind() 指定函数的初始参数 - 请参阅 Partially applied functions
    【解决方案3】:

    一个更简单的答案。

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

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

    作为@Mytskine pointed out 可能最好的深入解释是RFC for closures。 (为此给他点赞。)

    【讨论】:

    • use 语句中的 as 关键字在 php 5.5 中给我一个语法错误:$closure = function ($value) use ($localVar as $alias) { //stuff}; 给出的错误是:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
    • @KalZekdor,也被 php5.3 确认,似乎已被弃用。我更新了答案,感谢您的努力。
    • 我会在第 5 点添加这样的方式,修改像 &amp;$total 这样的指针的值也具有内部效果。换句话说,如果您在定义闭包之后更改$total的值outside,则新值只有在它是指针时才会被传入。
    • 这条线停止了我两个小时徒劳的搜索You can pass in variables as pointers like in case of &amp;$total. This way, modifying the value of $total DOES HAVE an external effect, the original variable's value changes.
    • 请向下滚动并查看此答案:stackoverflow.com/a/30547499/529187
    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

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

      没有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 变量引用与&amp;

      $s = "hello";
      $f = function () use (&$s) {
          echo $s;
      };
      
      $s = "how are you?";
      $f(); // how are you?
      

      【讨论】:

      • 在阅读完这篇文章后,我并不后悔再滚动一下,但猜想需要对第三块中的错字进行小幅编辑。应该有 $s 而不是 $obj。
      • 那是如何使用的一个很好的例子:)
      • 我希望这是最佳答案。
      • 我只想说这是最好的解释
      【解决方案6】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2021-10-12
        • 2015-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-05
        • 1970-01-01
        • 1970-01-01
        • 2015-11-11
        相关资源
        最近更新 更多