一、什么是闭包?

  • 首先闭包是一个函数,它能够读取其他函数内部的变量。
  • 一般的高级编程语言中,(不通过特殊方法)只有在函数内部才能够读取到其中的局部变量
  • 我们可以将闭包理解成一个定义在函数内部的子函数。
  • 本质上闭包是将函数内部与函数外部连接起来的桥梁

二、PHP中的闭包

1. 思考为什么要使用闭包?

面向对象的语言对代码的复用功能上:是用类来实现的,类中的代码复用使用继承来实现。那面向函数的代码复用是如何实现的?这就是为什么我们要使用闭包的原因,解决函数的复用问题。
PHP中的闭包和javascript很类似,通过使用 use 关键字来实现。

2. Closure类

查看手册,Closure类用于代表匿名函数。匿名函数在PHP5.3被引入,以前有一个create_function的函数. 它的结构是这样的 string create_function ( string $args , string $code ) ,第一个参数是我们创建的这个函数的参数,第二个参数是,我们将使用的代码结构。可是两个参数的类型都是String,你让一个稍微复杂的函数的函数体用引号来包裹一定1看起来就不优雅。所以就有了匿名函数 PHP 会自动将闭包函数转换成内置类Closure类的对象

  • Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
  • Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。

3. 测试 bind()

    1. 先创建一下基本的测试环境代码
  1. <?php
  2. // 创建一个用于测试的类
  3. class Product
  4. {
  5. public $name;
  6. protected $no;
  7. private $price;
  8. function __construct($name, $no, $price)
  9. {
  10. $this->name = $name;
  11. $this->no = $no;
  12. $this->price = $price;
  13. }
  14. public function getSummaryLine()
  15. {
  16. $base = '商品名称:' . $this->name;
  17. $base .= '<br>商品编号:' . $this->name;
  18. $base .= '<br>商品价格:' . $this->name;
  19. return $base;
  20. }
  21. }
  22. function show_format($variable)
  23. {
  24. echo "<pre>";
  25. print_r($variable);
  26. echo "<hr/></pre>";
  27. }
    1. 准备三个不同的匿名函数,来访问商品类的三个属性
      • 商品名称-public
      • 商品编号-no
      • 商品价格-price
  1. <?php
  2. // 匿名函数
  3. $funcName = function () {
  4. return $this->name;
  5. };
  6. $funcNo = function () {
  7. return $this->no;
  8. };
  9. $funcPrice = function () {
  10. return $this->price;
  11. };
  • 先使用 Closure::bind() ,来绑定第一个匿名函数变量$funcName,看看返回的结果.
  • 从返回结构上看,它是一个对象,确实绑定了Product类的$this的作用域
  1. <?php
  2. $res1 = Closure::bind($funcName, new Product('烤串', 1001, '10 RMB'), 'Product');
  3. show_format($res1);

image.png

  • 输出其中的属性
  • 我们发现都能够输出,无论是什么 public、protected、private,仔细一想,这不废话吗?它绑定后相遇与将Product类的伪变量$this的作用域是一样的
  1. <?php
  2. $res1 = Closure::bind($funcName, new Product('烤串', 1001, '10 RMB'), 'Product');
  3. show_format($res1());
  4. $res2 = Closure::bind($funcNo, new Product('烤串', 1001, '10 RMB'), 'Product');
  5. show_format($res2());
  6. $res3 = Closure::bind($funcPrice, new Product('烤串', 1001, '10 RMB'), 'Product');
  7. show_format($res3());

image.png

  • 我们稍稍改造一下Product类,看看它访问不同权限的修饰的方法的结果如何,不出意外的话应该和上面的结果如出一辙。
  1. <?php
  2. // 创建一个用于测试的类
  3. class Product
  4. {
  5. public function A()
  6. {
  7. return 'A()';
  8. }
  9. protected function B()
  10. {
  11. return 'B()';
  12. }
  13. private function C()
  14. {
  15. return 'C()';
  16. }
  17. }
  18. function show_format($variable)
  19. {
  20. echo "<pre>";
  21. print_r($variable);
  22. echo "<hr/></pre>";
  23. }
  24. // 匿名函数
  25. $funcA = function () {
  26. return $this->A();
  27. };
  28. $funcB = function () {
  29. return $this->B();
  30. };
  31. $funcC = function () {
  32. return $this->C();
  33. };
  34. $res1 = Closure::bind($funcA, new Product(), 'Product');
  35. show_format($res1());
  36. $res2 = Closure::bind($funcB, new Product(), 'Product');
  37. show_format($res2());
  38. $res3 = Closure::bind($funcC, new Product(), 'Product');
  39. show_format($res3());

image.png

  • 实际上如果我不加上第三个参数会报错
  • 所以可选的第三个参数如手册上面所说:绑定给闭包的类作用域, ‘static’ 表示不改变(static 也是默认值)。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。
  1. <?php
  2. $res3 = Closure::bind($funcB, new Product());
  3. show_format($res3());
  4. // Fatal error: Uncaught Error:
  5. // Call to protected method Product::B() from context 'Closure'
  • 上面说,如果传入一个对象,则使用这个对象的类型名称,我们来试一下.
  • 就结果来说,手册果然诚不欺我,就是PHP手册的解释和例子过于垃圾,不让人容易理解。
  1. <?php
  2. $res3 = Closure::bind($funcB, new Product(), new Product());
  3. show_format($res3());
  4. $res4 = Closure::bind($funcB, new Product(), 'Product');
  5. show_format($res4());

image.png

  • 闲着没事,我们继续改造一下Product类,来测试一下静态的方法。
  • 果然不出所料,是一样的效果。
  1. <?php
  2. <?php
  3. // 创建一个用于测试的类
  4. class Product
  5. {
  6. public static function A()
  7. {
  8. return 'A()';
  9. }
  10. protected static function B()
  11. {
  12. return 'B()';
  13. }
  14. private static function C()
  15. {
  16. return 'C()';
  17. }
  18. }
  19. function show_format($variable)
  20. {
  21. echo "<pre>";
  22. print_r($variable);
  23. echo "<hr/></pre>";
  24. }
  25. // 匿名函数
  26. $funcA = function () {
  27. return $this->A();
  28. };
  29. $funcB = function () {
  30. return $this->B();
  31. };
  32. $funcC = function () {
  33. return $this->C();
  34. };
  35. $res1 = Closure::bind($funcA, new Product(), 'Product');
  36. show_format($res1());
  37. $res2 = Closure::bind($funcB, new Product(), 'Product');
  38. show_format($res2());
  39. $res3 = Closure::bind($funcC, new Product(), new Product());
  40. show_format($res3());

image.png

4. 测试bindTo()

  • 熟悉bind()后我们再去看看bindTo(),感觉这两个方法应该差不多才对
  • public Closure Closure::bindTo ( object $newthis [, mixed $newscope = ‘static’ ] )
  • — 复制当前闭包对象,绑定指定的$this对象和类作用域。
  • 我们先来正常的使用,即使是这一个简单的例子我们也可也回顾到以前的好多知识点
  • PHP中对于Public修饰的方法还是很宽松的,就算它是静态的我们也可以使用对象来调用。反过来这个方法时public修饰的它不是静态的,我们其实也可以静态去调用。看起来这并不是我们所期望的
  • 一般调用静态方法
    • 在类的外部:我们使用类名::类方法来调用
    • 在类的内部:
      • 使用self::类方法来调用
      • 使用类名::类方法来调用
      • 使用static::类方法来调用也是可行,下一节需要仔细理解一下PHP中的静态延迟绑定.这里不说
    • 我们也是可以在类的内部这么创建对象
      • self className()
      • new className()
      • static className()
  1. <?php
  2. class Product
  3. {
  4. public static function A()
  5. {
  6. echo 'A() <hr/>';
  7. }
  8. protected static function B()
  9. {
  10. echo 'B() <hr/>';
  11. }
  12. private static function C()
  13. {
  14. echo 'C() <hr/>';
  15. }
  16. public static function B_B()
  17. {
  18. self::B();
  19. }
  20. public static function C_C()
  21. {
  22. static::C();
  23. }
  24. }
  25. function show_format($variable)
  26. {
  27. echo "<pre>";
  28. print_r($variable);
  29. echo "<hr/></pre>";
  30. }
  31. (new Product())->A();
  32. (new Product())->B_B();
  33. (new Product())->C_C();
  • 继续,我们使用 bindTo()来试一下
  • 使用手册上面的代码,没啥头绪,先暂停
  1. <?php
  2. <?php
  3. class A
  4. {
  5. function __construct($val)
  6. {
  7. $this->val = $val;
  8. }
  9. function getClosure()
  10. {
  11. //returns closure bound to this object and scope
  12. return function () {
  13. return $this->val;
  14. };
  15. }
  16. }
  17. $ob1 = new A(1);
  18. $res1 = $ob1->getClosure();
  19. echo $res1(), "<br>"; // 1
  20. $res2 = $res1->bindTo($ob1);
  21. echo $res2(), "<br>"; // 1

5. PHP中闭包和匿名函数的关系

  • 其实我们早就应该看出来,在PHP中匿名函数就是闭包,虽然在概念上这二者是根本不一样的
  • 返回值中 Closure Object,早就告诉了我们
  1. <?php
  2. <?php
  3. error_reporting(E_ALL);
  4. function print_format($vaeiable)
  5. {
  6. print_r('<pre>') . print_r($vaeiable) . print_r('</pre>');
  7. }
  8. $func1 = function ($a, $b) {
  9. return $a + $b;
  10. };
  11. print_format($func1);
  12. /*
  13. Closure Object
  14. (
  15. [parameter] => Array
  16. (
  17. [$a] =>
  18. [$b] =>
  19. )
  20. )
  21. */
  • 当然我们亦可以传递参数 print_format($func1(1,2)); // 3 就像调用普通函数一样

6. use一桥梁

  • 当匿名函数需要使用外部的变量时,如下代码会报错误,一个Notice的提醒,我们找不到这个变量。
  • 当然这都在我们的意料之中,因为普通函数也不行啊,这是函数的作用域的问题啊
  1. <?php
  2. $outVariable = 7;
  3. $func1 = function ($a, $b) {
  4. return $a + $b + $outVariable;
  5. };
  6. print_format($func1(1,2)); // 3
  • 我现在头铁,我偏不用 use 关键字,使用global关键字,试试匿名函数中能否行的通.
  • 结果当然是可行的了,在我们的意料之中啊
  1. <?php
  2. $outVariable = 7;
  3. function func1 ($a, $b) {
  4. global $outVariable;
  5. return $a + $b + $outVariable;
  6. };
  7. print_format(func1(1,2)); // 10
  • 然后我继续头铁,我们将变量定义为静态的全局变量
  • 在向一个奇怪的地方发展 。。。。。我的用法违背了初衷
  1. <?php
  2. $outVariable = 7;
  3. function func1 ($a, $b) {
  4. static $outVariable;
  5. return $a + $b + $outVariable++;
  6. };
  7. print_format(func1(1,2)); // 3
  8. print_format(func1(1,2)); // 4
  9. print_format(func1(1,2)); // 5
  10. print_format(func1(1,2)); // 6
  • 我们使用use来写
  • 代码如下,所以对于PHP中的匿名函数(闭包),use就像是一个桥梁一样
  1. <?php
  2. $outVariable = 7;
  3. $func1 = function ($a, $b) use ($outVariable) {
  4. return $a + $b + $outVariable;
  5. };
  6. print_format($func1(1, 2)); // 10
  • 使用use ,后匿名函数能否改变外部变量,代码如下
  • 果然这么使用是不行的,意料之中
  1. <?php
  2. $outVariable = 0;
  3. $func1 = function () use ($outVariable) {
  4. return ++$outVariable;
  5. };
  6. print_format($func1()); // 1
  7. print_format($func1()); // 1
  8. print_format($func1()); // 1
  • 如果加上取地址符会怎么样?
  • 果然可行,这就说明了一个问题 use 关键字其实就相当于拷贝了外部变量的副本,就像是 clone() 方法一样,并不是变量的引用地址
  1. <?php
  2. $outVariable = 0;
  3. $func1 = function () use (&$outVariable) {
  4. return ++$outVariable;
  5. };
  6. print_format($func1()); // 1
  7. print_format($func1()); // 2
  8. print_format($func1()); // 3
  • 对于多个变量也是可以
  1. <?php
  2. $outVariable1 = 1;
  3. $outVariable2 = 2;
  4. $func1 = function () use ($outVariable1,$outVariable2) {
  5. return $outVariable1 + $outVariable2;
  6. };
  7. print_format($func1()); // 3

7.小结一PHP中的闭包

  • PHP中将匿名函数和闭包视作同一概念,虽然这并不是的
  • PHP中的闭包、匿名函数、不同函数都是看起来函数,句法相同。闭包和匿名函数其实是Closure类对象的实例,只不过PHP表面上伪装了一下
  • 闭包函数是为了函数的复用,闭包是函数外部于内部连接的桥梁。PHP中匿名函数+use实现了闭包,其实将匿名函数当参数传入也相当于实现了闭包。
  • PHP中的闭包是对象,通过伪变量$this,可以获取闭包内部的状态。它的内部没有属性,只有三个我们常用的方法
  • 后两个方法大致类似,注意最后一个参数是用来设定作用域的
  • 闭包对象实现了 __invloek()这个魔术方法
  • use 是匿名函数连接外部的桥梁,它仅仅拷贝的事变量的值,而非引用,如果实头铁,加上取地址符就好了。

三、PHP中哪些地方会用到闭包函数

1.返回一个函数结构

  • 使用array_reduce()函数求和,一般的函数使用
  1. <?php
  2. $arr = [1, 2, 3, 4, 5];
  3. function sum($arr)
  4. {
  5. return array_reduce($arr, function ($x, $y) {
  6. return $x + $y;
  7. });
  8. }
  9. print_r(sum($arr));
  • 使用闭包
  1. <?php
  2. $arr = [1, 2, 3, 4, 5];
  3. function lazySum($arr)
  4. {
  5. return function () use ($arr) {
  6. return array_reduce($arr, function ($x, $y){
  7. return $x + $y;
  8. });
  9. };
  10. }
  11. print_r(lazySum($arr)());
  • PHP中的一道闭包面试题
  1. <?php
  2. function plus()
  3. {
  4. $funcArr = [];
  5. for ($i = 1; $i <= 3; $i++) {
  6. $funcArr[] = function () use (&$i) {
  7. return $i * $i;
  8. };
  9. }
  10. return $funcArr;
  11. }
  12. $res = plus();
  13. var_dump($res[0]()); // 16
  14. var_dump($res[1]()); // 16
  15. var_dump($res[2]()); // 16

四、和JS中的闭包做对比孰强孰弱一当然是JS