经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » PHP » 查看文章
还不知道PHP有闭包?那你真OUT了
来源:cnblogs  作者:硬核项目经理  时间:2021/1/18 16:37:27  对本文有异议

做过一段时间的Web开发,我们都知道或者了解JavaScript中有个非常强大的语法,那就是闭包。其实,在PHP中也早就有了闭包函数的功能。早在5.3版本的PHP中,闭包函数就已经出现了。到了7以及后来的现代框架中,闭包函数的使用更是无处不在。在这里,我们就先从基础来了解PHP中闭包的使用吧!

闭包函数(closures)在PHP中都会转换为 Closure 类的实例。在定义时如果是赋值给变量,在结尾的花括号需要添加;分号。闭包函数从父作用域中继承变量,任何此类变量都应该用 use 语言结构传递进去。 PHP 7.1 起,不能传入此类变量:superglobals、 $this 或者和参数重名。

基础语法

闭包的使用非常简单,和JavaScript也非常相似。因为他们都有另外一个别名,叫做匿名函数。

  1. $a = function () {
  2. echo "this is testA";
  3. };
  4. $a(); // this is testA
  5. function testA ($a) {
  6. var_dump($a);
  7. }
  8. testA($a); // class Closure#1 (0) {}
  9. $b = function ($name) {
  10. echo 'this is ' . $name;
  11. };
  12. $b('Bob'); // this is Bob

我们将$a和$b两个变量直接赋值为两个函数。这样我们就可以使用变量()的形式调用这两个函数了。通过testA()方法,我们可以看出闭包函数是可以当做普通参数传递的,因为它自动转换成为了 Closure 类的实例。

  1. $age = 16;
  2. $c = function ($name) {
  3. echo 'this is ' . $name . ', Age is ' . $age;
  4. };
  5. $c('Charles'); // this is Charles, Age is
  6. $c = function ($name) use ($age) {
  7. echo 'this is ' . $name . ', Age is ' . $age;
  8. };
  9. $c('Charles'); // this is Charles, Age is 16

如果我们需要调用外部的变量,需要使用use关键字来引用外部的变量。这一点和普通函数不一样,因为闭包有着严格的作用域问题。对于全局变量来说,我们可以使用use,也可以使用global。但是对于局部变量(函数中的变量)时,只能使用use。这一点我们后面再说。

作用域

  1. function testD(){
  2. global $testOutVar;
  3. echo $testOutVar;
  4. }
  5. $d = function () use ($testOutVar) {
  6. echo $testOutVar;
  7. };
  8. $dd = function () {
  9. global $testOutVar;
  10. echo $testOutVar;
  11. };
  12. $testOutVar = 'this is d';
  13. $d(); // NULL
  14. testD(); // this is d
  15. $dd(); // this is d
  16. $testOutVar = 'this is e';
  17. $e = function () use ($testOutVar) {
  18. echo $testOutVar;
  19. };
  20. $e(); // this is e
  21. $testOutVar = 'this is ee';
  22. $e(); // this is e
  23. $testOutVar = 'this is f';
  24. $f = function () use (&$testOutVar) {
  25. echo $testOutVar;
  26. };
  27. $f(); // this is f
  28. $testOutVar = 'this is ff';
  29. $f(); // this is ff

在作用域中,use传递的变量必须是在函数定义前定义好的,从上述例子中可以看出。如果闭包($d)是在变量($testOutVar)之前定义的,那么$d中use传递进来的变量是空的。同样,我们使用global来测试,不管是普通函数(testD())或者是闭包函数($dd),都是可以正常使用$testOutVar的。

在$e函数中的变量,在函数定义之后进行修改也不会对$e闭包内的变量产生影响。这时候,必须要使用引用传递($f)进行修改才可以让闭包里面的变量产生变化。这里和普通函数的引用传递与值传递的概念是相同的。

除了变量的use问题,其他方面闭包函数和普通函数基本没什么区别,比如进行类的实例化:

  1. class G
  2. {}
  3. $g = function () {
  4. global $age;
  5. echo $age; // 16
  6. $gClass = new G();
  7. var_dump($gClass); // G info
  8. };
  9. $g();

类中作用域

关于全局作用域,闭包函数和普通函数的区别不大,主要的区别体现在use作为桥梁进行变量传递时的状态。在类方法中,有没有什么不一样的地方呢?

  1. $age = 18;
  2. class A
  3. {
  4. private $name = 'A Class';
  5. public function testA()
  6. {
  7. $insName = 'test A function';
  8. $instrinsic = function () {
  9. var_dump($this); // this info
  10. echo $this->name; // A Class
  11. echo $age; // NULL
  12. echo $insName; // null
  13. };
  14. $instrinsic();
  15. $instrinsic1 = function () {
  16. global $age, $insName;
  17. echo $age; // 18
  18. echo $insName; // NULL
  19. };
  20. $instrinsic1();
  21. global $age;
  22. $instrinsic2 = function () use ($age, $insName) {
  23. echo $age; // 18
  24. echo $insName; // test A function
  25. };
  26. $instrinsic2();
  27. }
  28. }
  29. $aClass = new A();
  30. $aClass->testA();
  • A::testA()方法中的$insName变量,我们只能通过use来拿到。
  • 闭包函数中的$this是调用它的环境的上下文,在这里就是A类本身。闭包的父作用域是定义该闭包的函数(不一定是调用它的函数)。静态闭包函数无法获得$this。
  • 全局变量依然可以使用global获得。

小技巧

了解了闭包的这些特性后,我们可以来看几个小技巧:

  1. $arr1 = [
  2. ['name' => 'Asia'],
  3. ['name' => 'Europe'],
  4. ['name' => 'America'],
  5. ];
  6. $arr1Params = ' is good!';
  7. // foreach($arr1 as $k=>$a){
  8. // $arr1[$k] = $a . $arr1Params;
  9. // }
  10. // print_r($arr1);
  11. array_walk($arr1, function (&$v) use ($arr1Params) {
  12. $v .= ' is good!';
  13. });
  14. print_r($arr1);

干掉foreach:很多数组类函数,比如array_map、array_walk等,都需要使用闭包函数来处理。上例中我们就是使用array_walk来对数组中的内容进行处理。是不是很有函数式编程的感觉,而且非常清晰明了。

  1. function testH()
  2. {
  3. return function ($name) {
  4. echo "this is " . $name;
  5. };
  6. }
  7. testH()("testH's closure!"); // this is testH's closure!

看到这样的代码也不要懵圈了。PHP7支持立即执行语法,也就是JavaScript中的IIFE(Immediately-invoked function expression)。

我们再来一个计算斐波那契数列的:

  1. $fib = function ($n) use (&$fib) {
  2. if ($n == 0 || $n == 1) {
  3. return 1;
  4. }
  5. return $fib($n - 1) + $fib($n - 2);
  6. };
  7. echo $fib(10);

同样的还是使用递归来实现。这里直接换成了闭包递归来实现。最后有一点要注意的是,use中传递的变量名不能是带下标的数组项:

  1. $fruits = ['apples', 'oranges'];
  2. $example = function () use ($fruits[0]) { // Parse error: syntax error, unexpected '[', expecting ',' or ')'
  3. echo $fruits[0];
  4. };
  5. $example();

这样写直接就是语法错误,无法成功运行的。

彩蛋

Laravel中的IoC服务容器中,大量使用了闭包能力,我们模拟一个便于大家理解。当然,更好的方案是自己去翻翻Laravel的源码。

  1. class B
  2. {}
  3. class C
  4. {}
  5. class D
  6. {}
  7. class Ioc
  8. {
  9. public $objs = [];
  10. public $containers = [];
  11. public function __construct()
  12. {
  13. $this->objs['b'] = function () {
  14. return new B();
  15. };
  16. $this->objs['c'] = function () {
  17. return new C();
  18. };
  19. $this->objs['d'] = function () {
  20. return new D();
  21. };
  22. }
  23. public function bind($name)
  24. {
  25. if (!isset($this->containers[$name])) {
  26. if (isset($this->objs[$name])) {
  27. $this->containers[$name] = $this->objs[$name]();
  28. } else {
  29. return null;
  30. }
  31. }
  32. return $this->containers[$name];
  33. }
  34. }
  35. $ioc = new Ioc();
  36. $bClass = $ioc->bind('b');
  37. $cClass = $ioc->bind('c');
  38. $dClass = $ioc->bind('d');
  39. $eClass = $ioc->bind('e');
  40. var_dump($bClass); // B
  41. var_dump($cClass); // C
  42. var_dump($dClass); // D
  43. var_dump($eClass); // NULL

总结

闭包特性经常出现的地方是事件回调类的功能中,另外就是像彩蛋中的IoC的实现。因为闭包有一个很强大的能力就是可以延迟加载。IoC的例子我们的闭包中返回的是新new出来的对象。当我们的程序运行的时候,如果没有调用$ioc->bind('b'),那么这个B对象是不会创建的,也就是说这时它还不会占用资源占用内存。而当我们需要的时候,从服务容器中拿出来的时候才利用闭包真正的去创建对象。同理,事件的回调也是一样的概念。事件发生时在我们需要处理的时候才去执行回调里面的代码。如果没有闭包的概念,那么$objs容器就这么写了:

  1. $this->objs['b'] = new B();
  2. $this->objs['c'] = new C();
  3. $this->objs['d'] = new D();

容器在实例化的时候就把所有的类都必须实例化了。这样对于程序来说很多用不上的对象就都被创建了,带来非常大的资源浪费。

基于闭包的这种强大能力,现在闭包函数已经在Laravel、TP6等框架中无处不在了。学习无止尽,掌握原理再去学习框架往往更能事半功倍。

测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/201911/source/%E8%BF%98%E4%B8%8D%E7%9F%A5%E9%81%93PHP%E6%9C%89%E9%97%AD%E5%8C%85%EF%BC%9F%E9%82%A3%E4%BD%A0%E7%9C%9FOUT%E4%BA%86.php

参考文档:
https://www.php.net/manual/zh/functions.anonymous.php
https://www.php.net/manual/zh/functions.anonymous.php#100545
https://www.php.net/manual/zh/functions.anonymous.php#119388

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

原文链接:http://www.cnblogs.com/zyblog-coder/p/14291304.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号