PHP V5.3 中的新特性,第 2 部分: 闭包及 lambda 函数

简介: 闭包函数和 lambda 函数绝对不是新出现的概念;它们均来自函数编程领域。函数编程 是一种编程风格,它将关注点从执行命令转移到表达式计算。这些表达式是使用函数构成的,结合这些函数可以得到我们要查找的结果。

闭包函数和 lambda 函数绝对不是新出现的概念;它们均来自函数编程领域。函数编程 是一种编程风格,它将关注点从执行命令转移到表达式计算。这些表达式是使用函数构成的,结合这些函数可以得到我们要查找的结果。这种编程风格最常用于学术目的,但是也可以在人工智能与数学领域中见到,并且可以在用 Erlang、Haskell 及 Scheme 等语言编写的商业应用程序中找到。

闭包 最初是在 20 世纪 60 年代作为 Scheme 的一部分开发的,Scheme 是最著名的函数编程语言之一。Lambda 函数和闭包通常出现在允许将函数处理为第一类值(First-class value)的语言中,这意味着函数可以动态创建并作为参数传递给其他语言。

从那时起,闭包及 lambda 函数已经找到了走出函数编程世界并进入 JavaScript、Python 和 Ruby 等语言的方法。JavaScript 是支持闭包和 lambda 函数的最常见语言之一。JavaScript 实际使用这些函数作为支持面向对象的编程方法,把函数嵌套到其他函数中以用作私有成员。清单 1 提供了 JavaScript 如何使用闭包的示例。


清单 1. 使用闭包构建 JavaScript 对象

				
var Example = function()
{ 
    this.public = function() 
    { 
        return "This is a public method"; 
    }; 

    var private = function() 
    { 
        return "This is a private method"; 
    };
};

Example.public()  // returns "This is a public method" 
Example.private() // error - doesn't work

 

如清单 1 中所示,Example 对象的成员函数被定义为闭包。由于私有方法作用于局部变量(与绑定到使用此关键字的 Example 对象的公共方法相反),因此从外部看不到它。

现在我们已经了解了这些概念的历史,让我们查看 PHP 中的 lambda 函数。lambda 函数的概念是闭包的基础,并且提供了一种比 PHP 中已有的 create_function() 函数改进了很多的动态创建函数的方法。

Lambda 函数

Lambda 函数(或者通常所谓的 “匿名函数”)是可以随时定义的简单抛弃型函数,并且通常都与变量绑定。函数本身仅存在于定义函数的变量范围内,因此当该变量超出范围时,函数也超出范围。lambda 函数的理念源于 20 世纪 30 年代的数学研究。它被称为 lambda 演算,用于研究函数定义与应用程序以及递归概念。lambda 演算用于开发函数编程语言,例如 Lisp 和 Scheme。

对于大多数实例来说,尤其对于接受回调函数的许多 PHP 函数来说,Lambda 函数非常方便。array_map() 就是这样一种函数,它允许我们遍历数组并将回调函数应用到数组的每个元素上。在早期版本的 PHP 中,这些函数的最大问题是没有一种清晰的方式定义回调函数;我们坚持使用以下三种解决方法的其中一种:

  1. 我们可以在代码中的其他位置定义回调函数,因此我们知道它可用。这有些麻烦,因为它把调用的实现部分移到了其他位置,这样做对于可读性与可维护性极为不便,尤其是不打算在其他位置使用此函数时。
  2. 我们可以在同一个代码块中定义回调函数,但是使用一个名称。虽然这样做有助于把内容放在一起,但是需要在定义周围添加if 块以避免名称空间冲突。清单 2 展示了这种方法。


    清单 2. 在同一个代码块中定义指定的回调
    						
    function quoteWords()
    {
         if (!function_exists ('quoteWordsHelper')) {
             function quoteWordsHelper($string) {
                 return preg_replace('/(\w)/','"$1"',$string);
             }
          }
          return array_map('quoteWordsHelper', $text);
    }
  3. 我们可以使用 create_function()(从 V4 开始就是 PHP 的一部分)在运行时创建函数。虽然在功能上此函数执行了所需操作,但是它有一些缺点。一个主要缺点是,它在运行时而非编译时编译,不允许操作码缓存来缓存函数。它的语法智能(syntax-wise)也非常糟糕,并且大多数 IDE 中的字符串高亮显示功能完全不起作用。

虽然接受回调函数的函数功能十分强大,但是没有一种好方法可以执行一次性回调函数,而无需执行一些非常笨拙的工作。使用 PHP V5.3,我们可以使用 lambda 函数以更规则的方法重新执行以上示例。


清单 3. 使用 lambda 函数用于回调的 quoteWords()

				
function quoteWords()
{
     return array_map('quoteWordsHelper',
            function ($string) {
                return preg_replace('/(\w)/','"$1"',$string);
            });
}

 

我们看到了定义这些函数的更规则的语法,这可以通过操作码缓存来优化性能。我们也已经得到了改进的可读性以及与字符串高亮显示功能的兼容性。让我们在此基础上了解如何在 PHP 中使用闭包。

 

回页首

闭包

Lambda 函数本身并没有添加以前不能执行的功能。正如我们所见,我们可以使用 create_function() 执行所有这项操作,尽管后果是使用更糟糕的语法并且性能更加不理想。但是它们仍然是抛弃型函数并且不维护任何类型的状态,这限制了我们可以用它们执行的操作。因此出现了闭包并使 lambda 函数得到增强。

闭包是在它自己的环境中执行计算的函数,它有一个或多个绑定变量可以在调用函数时访问。它们来自函数编程世界,其中涉及大量概念。闭包类似于 lambda 函数,但是在与定义闭包的外部环境中的变量进行交互方面更加智能。

让我们看一看如何在 PHP 中定义闭包。清单 4 显示了从外部环境导入变量并将其简单地输出到屏幕上的闭包示例。


清单 4. 简单闭包示例

				
$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Output:
Hello World!

 

从外部环境中导入的变量是在闭包函数定义的 use 子句中指定的。默认情况下,它们是由值传递的,意味着如果要更新传递到闭包函数定义内的值,则不更新外部值。但是,我们可以通过在变量前放置 & 运算符来完成此操作,这种方法在函数定义中用于表示按引用传递。清单 5 显示了这种方法的示例。


清单 5. 通过引用传递变量的闭包

				
$x = 1
$closure = function() use (&$x) { ++$x; }

echo $x . "\n";
$closure();
echo $x . "\n";
$closure();
echo $x . "\n";

Output:
1
2
3

 

可以看到,闭包使用外部变量 $x 并在每次调用闭包时递增该变量。我们可以将按值和按引用传递的变量轻松地混合到 use 子句中,并且可以顺利地处理这些变量。我们也可以拥有直接返回闭包的函数,如清单 6 所示。在本例中,闭包的生命周期实际上比定义闭包的方法长。


清单 6. 函数返回的闭包

				
function getAppender($baseString)
{
      return function($appendString) use ($baseString) { return $baseString . 
$appendString; };
}

 

 

回页首

闭包与对象

闭包不但是过程式编程的有用工具,而且是面向对象编程的有用工具。在这种情况下使用闭包与在类外部使用闭包实现的目的相同:包含在小范围内绑定的特定函数。在对象外部与在对象内部使用一样简单。

在对象内定义时,非常方便的一点是闭包通过 $this 变量拥有对对象的完全访问权,而无需显式导入。清单 7 演示了该示例。


清单 7. 对象内的闭包

				
class Dog
{
    private $_name;
    protected $_color;

    public function __construct($name, $color)
    {
         $this->_name = $name;
         $this->_color = $color;
    }

    public function greet($greeting)
    {
         return function() use ($greeting) {
             echo "$greeting, I am a {$this->_color} dog named 
{$this->_name}.";
         };
    }
}

$dog = new Dog("Rover","red");
$dog->greet("Hello");

Output:
Hello, I am a red dog named Rover.

 

在这里,我们在闭包内显式使用提供给 greet() 方法的欢迎词,闭包在该方法内定义。我们还在闭包内获取狗的颜色和名字,传递到构造函数中并存储到对象中。

在类中定义的闭包基本上与在对象外部定义的闭包相同。惟一的不同之处在于通过 $this 变量自动导入对象。我们可以通过将闭包定义为静态闭包禁用此行为。


清单 8. 静态闭包

				
class House
{
     public function paint($color)
     {
         return static function() use ($color) { echo "Painting the 
house $color...."; };
     }
}

$house = new House();
$house->paint('red');

Output:
Painting the house red....

 

此示例类似于清单 5 中定义的 Dog 类。最大的差别是在闭包内不使用对象的任何属性,因为它被定义为静态类。

在对象内使用静态闭包与使用非静态闭包相比的最大优点是节省内存。由于无需将对象导入闭包中,因此可以节省大量内存,尤其是在拥有许多不需要此功能的闭包时。

针对对象的另一个优点是添加名为 __invoke() 的魔术方法,此方法允许对象本身被调用为闭包。如果定义了此方法,则在该上下文中调用对象时将使用此方法。清单 9 演示了示例。


清单 9. 使用 __invoke() 方法

				

class Dog
{
    public function __invoke()
    {
         echo "I am a dog!";
    }
}

$dog = new Dog();
$dog();

 

将清单 9 中所示的对象引用调用为变量将自动调用 __invoke() 魔术方法,使类本身用作闭包。

闭包可以很好地与面向对象的代码以及面向过程的代码整合。让我们看一看闭包如何与 PHP 的强大 Reflection API 交互。

 

回页首

闭包与反射

PHP 有一个有用的反射 API,它允许我们对类、接口、函数和方法执行反向工程。按照设计,闭包是匿名函数,这意味着它们不显示在反射 API 中。

但是,新 getClosure() 方法已经添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 类中,可以从指定的函数或方法动态创建闭包。它在此上下文中用作宏,通过闭包调用函数方法将在定义它的上下文中执行函数调用。清单 10 显示了此方法如何运行。


清单 10. 使用 getClosure() 方法

				
class Counter
{
      private $x;

      public function __construct()
      {
           $this->x = 0;
      }

      public function increment()
      {
           $this->x++;
      }

      public function currentValue()
      {
           echo $this->x . "\n";
      }
}
$class = new ReflectionClass('Counter');
$method = $class->getMethod('currentValue');
$closure = $method->getClosure()
$closure();
$class->increment();
$closure();
Output:
0
1

 

这种方法的一个有趣的副作用是允许通过闭包访问类的私有成员和受保护成员,这有利于对类执行单元测试。清单 11 展示了对类的私有方法的访问。


清单 11. 访问类的私有方法

				
class Example 
{ 
     ....
     private static function secret() 
     { 
          echo "I'm an method that's hiding!"; 
     } 
     ...
} 

$class = new ReflectionClass('Example');
$method = $class->getMethod('secret');
$closure = $method->getClosure()
$closure();
Output:
I'm an method that's hiding!

 

此外,可以使用反射 API 来内省(introspect)闭包本身,如清单 12 所示。只需将对闭包的变量引用传递到 ReflectionMethod 类的构造函数中。


清单 12. 使用反射 API 内省闭包

				
$closure = function ($x, $y = 1) {}; 
$m = new ReflectionMethod($closure); 
Reflection::export ($m);
Output:
Method [ <internal> public method __invoke ] {
  - Parameters [2] {
    Parameter #0 [ <required> $x ]
    Parameter #1 [ <optional> $y ]
  }
}

 

关于向后兼容性值得注意的一点是,PHP 引擎现在保留类名 Closure 并用于存储闭包,因此使用该名称的所有类都需要重命名。

正如我们所见,反射 API 能够通过现有函数和方法动态创建闭包,从而为闭包提供强大的支持。它们还可以像普通函数一样内省到闭包中。

 

回页首

为什么使用闭包?

如在 lambda 函数的示例中所示,闭包的最明显用法之一是少数 PHP 函数接受回调函数作为参数。但是,在需要把逻辑封装到自己的范围内的情况下,闭包会十分有用。重构旧代码以进行简化并提高可读性就是这样一个例子。查看以下示例,该示例显示了在运行一些 SQL 查询时使用的记录程序。


清单 13. 记录 SQL 查询的代码

				
$db = mysqli_connect("server","user","pass"); 
Logger::log('debug','database','Connected to database'); 
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails'); 
Logger::log('debug','database','Insert Hammer into to parts table'); 
$db->query('insert into parts (part, description) values 
      ('Drill','Puts holes in wood');
Logger::log('debug','database','Insert Drill into to parts table'); 
$db->query('insert into parts (part, description) values ('Saw','Cuts wood'); 
Logger::log('debug','database','Insert Saw into to parts table'); 

 

从清单 13 中可以看出执行操作的重复程度。对 Logger::log() 执行的每次调用都有相同的前两个实参。为了解决此问题,我们可以把该方法调用放入闭包并转而针对该闭包执行调用。得到的代码如下所示:


清单 14. 记录 SQL 查询的重构代码

				
$logdb = function ($string) { Logger::log('debug','database',$string); };
$db = mysqli_connect("server","user","pass"); 
$logdb('Connected to database'); 
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails'); 
$logdb('Insert Hammer into to parts table'); 
$db->query('insert into parts (part, description) values 
       ('Drill','Puts holes in wood');
$logdb('Insert Drill into to parts table'); 
$db->query('insert into parts (part, description) values ('Saw','Cuts wood'); 
$logdb('Insert Saw into to parts table'); 

 

代码不但在外观上更加规则,而且更易于更改 SQL 查询日志的日志级别,因为现在只需要在一个位置进行更改。

 

回页首

结束语

本文演示了闭包在 PHP V5.3 代码中作为函数编程构造时多么有用。我们讨论了 lambda 函数及闭包与这些函数相比的优点。对象与闭包可以很好地结合使用,比如我们在面向对象的代码内对闭包的特殊处理。我们看到了如何使用反射 API 创建动态闭包,以及如何内省现有的闭包。

目录
相关文章
|
4月前
|
PHP 开发者 UED
深入理解PHP 7的新特性及其性能优化
【7月更文挑战第26天】本文将深入探讨PHP 7版本引入的诸多新特性,并着重分析这些变化如何影响应用程序的性能。我们将通过具体示例和实际应用场景来展示PHP 7在提升开发效率和运行速度方面的优势,同时提供一些实用的性能优化技巧。
47 3
|
4月前
|
缓存 编译器 测试技术
PHP 8新特性解析与应用
在软件开发的广阔天地中,PHP始终是一颗耀眼的星辰。随着PHP 8的发布,一系列激动人心的新特性为开发者带来了前所未有的编程体验。本文将深入探讨PHP 8中的JIT编译器、联合类型、命名参数、匹配表达式等关键特性,并通过实例分析它们如何优化代码结构、提升执行效率。我们将一起见证PHP 8如何开启现代化PHP开发的新篇章,并为读者提供实用的技术参考。
39 2
|
4月前
|
安全 IDE 编译器
深入理解PHP 7的新特性及其对现代Web开发的影响
【7月更文挑战第30天】本文将深入探索PHP 7版本中引入的关键新特性,并分析这些改进如何优化现代Web开发实践。通过对比PHP 5和PHP 7的性能差异,我们将揭示PHP 7如何提升应用响应速度和资源利用效率。此外,本文还将讨论PHP 7对开发者工作流程的影响,包括新的语言特性、错误处理机制以及内置函数的增强,旨在为读者提供全面了解PHP 7所带来的变革性影响。
|
3月前
|
设计模式 数据处理 PHP
探索PHP中的匿名函数与闭包
【8月更文挑战第3天】本文将深入探讨PHP中匿名函数的概念、用法及其在实际应用中的便捷性。通过代码示例,我们将了解如何声明和运用匿名函数,以及闭包如何在其上下文中捕获变量。文章旨在为读者提供一种灵活的编程工具,以简化代码并增强其可读性和可维护性。
|
3月前
|
PHP 开发者
探索PHP中的匿名函数和闭包
【8月更文挑战第31天】在PHP的世界中,匿名函数和闭包就像是神秘的魔法,它们让代码变得更加简洁和强大。本文将带你揭开它们的面纱,通过实际代码示例,展示如何在PHP中灵活运用这些功能,以及如何优化你的代码结构。
|
3月前
|
PHP 开发者
探索PHP中的匿名函数和闭包
【8月更文挑战第31天】 本文通过浅显易懂的语言和直观的代码实例,带你了解PHP中匿名函数的概念、使用场景以及闭包的强大功能。我们不仅会探讨它们如何简化日常编码,还将深入理解它们的工作原理和在复杂应用中的实际应用价值。无论你是PHP新手还是资深开发者,这篇文章都将为你打开一扇通往更高阶编程思维的门。
|
3月前
|
设计模式 PHP 开发者
探索PHP中的匿名函数与闭包
【8月更文挑战第31天】本文将带你了解PHP的匿名函数——一种简洁、高效的代码编写方式,并深入探讨其背后的闭包概念。我们将通过实际示例,展示如何利用这些特性来优化你的PHP代码,同时也会解释它们在实际应用中的重要性和潜在价值。准备好让你的PHP代码变得更加灵活和强大了吗?让我们开始吧!
|
4月前
|
存储 SQL 编译器
PHP 8新特性深度解析与实战应用
本文将深入探讨PHP 8的新增特性,并结合实际案例演示如何有效利用这些特性优化现有项目。通过本文,您将了解到PHP 8带来的性能提升、安全性增强以及代码简化等方面的改进,以及如何将这些新特性融入日常开发工作之中。 【7月更文挑战第29天】
53 8
|
4月前
|
缓存 安全 测试技术
深入PHP 7:新特性与性能提升解析
在PHP 7的发布中,我们见证了一系列令人兴奋的性能改进和新特性的加入。本文将深入探讨这些变化如何影响开发者的日常编程实践,并展示通过实际例子如何最大化利用PHP 7的优势。准备好迎接代码效率和开发体验的全新升级!
|
4月前
|
安全 API PHP
深入理解PHP 7的新特性及其对现代Web开发的影响
【7月更文挑战第29天】本文将探索PHP 7版本引入的一系列新特性,并分析它们如何革新了现代Web开发。我们将从性能提升、语言特性增强、以及面向对象编程的改进等方面进行详细讨论,旨在为开发者提供一份全面的PHP 7新特性指南,帮助他们更好地利用这些新工具优化和加速Web应用的开发。
下一篇
无影云桌面