专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »PHP教程 » javascript闭包:PHP V5.3 中的新特性 第 2 部分: 闭包及 lambda 函数 »正文

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

来源: 发布时间:星期三, 2009年1月21日 浏览:185次 评论:0
  闭包和 lambda 绝对不是新出现概念;它们均来自编程领域编程 是种编程风格它将关注点从执行命令转移到表达式计算这些表达式是使用构成结合这些可以得到我们要查找结果这种编程风格最常用于学术目但是也可以在人工智能和数学领域中见到并且可以在用 Erlang、Haskell 及 Scheme 等语言编写商业应用中找到

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

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

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

var Example = function
{
  this.public = function
  {
     "This is a public method";
  };
  var private = function
  {
     "This is a private method";
  };
};
Example.public // s "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 非常方便.gif' />_map 就是这样它允许我们遍历并将回调应用到每个元素上在早期版本 PHP 中这些最大问题是没有种清晰方式定义回调;我们坚持使用以下 3种解决思路方法其中种:

  我们可以在代码中其他位置定义回调因此我们知道它可用这有些麻烦它把实现部分移到了其他位置这样做对于可读性和可维护性极为不便尤其是不打算在其他位置使用此

  我们可以在同个代码块中定义回调但是使用个名称虽然这样做有助于把内容放在但是需要在定义周围添加 块以避免名称空间冲突清单 2 展示了这种思路方法

  清单 2. 在同个代码块中定义指定回调

function quoteWords
{
   (!function_exists ('quoteWordsHelper')) {
     function quoteWordsHelper($) {
       preg_replace('/(w)/','"$1"',$);
     }
   }
    .gif' />_map('quoteWordsHelper', $text);
}


  我们可以使用 create_function(从 V4 开始就是 PHP 部分)在运行时创建虽然在功能上此执行了所需操作但是它有些缺点个主要缺点是它在运行时而非编译时编译不允许操作码缓存Cache来缓存Cache语法智能(syntax-wise)也非常糟糕并且大多数 IDE 中串高亮显示功能完全不起作用

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

  清单 3. 使用 lambda 用于回调 quoteWords

function quoteWords
{
   .gif' />_map('quoteWordsHelper',
      function ($) {
         preg_replace('/(w)/','"$1"',$);
      });
}


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

  闭包

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

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

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

  清单 4. 简单闭包举例

$ = "Hello World!";
$closure = function use ($) { echo $; };
$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)
{
    function($appendString) use ($baseString) { $baseString .
$appendString; };
}


  闭包和对象

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

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

  清单 7. 对象内闭包

Dog
{
  private $_name;
  protected $_color;
  public function __construct($name, $color)
  {
     $this->_name = $name;
     $this->_color = $color;
  }
  public function greet($greeting)
  {
     function use ($greeting) {
       echo "$greeting, I am a {$this->_color} dog named
{$this->_name}.";
     };
  }
}
$dog = Dog("Rover","red");
$dog->greet("Hello");
Output:
Hello, I am a red dog named Rover.


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

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

  清单 8. 静态闭包

House
{
   public function pa($color)
   {
     function use ($color) { echo "Paing the
house $color...."; };
   }
}
$house = House;
$house->pa('red');
Output:
Paing the house red....


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

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

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

  清单 9. 使用 __invoke 思路方法

Dog
{
  public function __invoke
  {
     echo "I am a dog!";
  }
}
$dog = Dog;
$dog;


  将清单 9 中所示对象引用为变量将自动 __invoke 魔术思路方法使类本身用作闭包

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

  闭包和反射

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

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

  清单 10. 使用 getClosure 思路方法

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


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

  清单 11. 访问类私有思路方法

Example
{
   ....
   private function secret
   {
     echo "I'm an method that's hiding!";
   }
   ...
}
$ = ReflectionClass('Example');
$method = $->getMethod('secret');
$closure = $method->getClosure
$closure;
Output:
I'm an method that's hiding!


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

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

$closure = function ($x, $y = 1) {};
$m = ReflectionMethod($closure);
Reflection::export ($m);
Output:
Method [ <ernal> 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 o parts (part, description) values ('Hammer','Pounds nails');
Logger::log('debug','database','Insert Hammer o to parts table');
$db->query('insert o parts (part, description) values
   ('Drill','Puts holes in wood');
Logger::log('debug','database','Insert Drill o to parts table');
$db->query('insert o parts (part, description) values ('Saw','Cuts wood');
Logger::log('debug','database','Insert Saw o to parts table');


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

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

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




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

  结束语

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



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: