-
博文分类专栏
- Jquery基础教程
-
- 文章:(15)篇
- 阅读:47263
- shell命令
-
- 文章:(42)篇
- 阅读:156702
- Git教程
-
- 文章:(36)篇
- 阅读:237796
- leetCode刷题
-
- 文章:(76)篇
- 阅读:137248
-
PHP 闭包的使用2018-02-10 19:23 阅读(6971) 评论(0)
一、简介
闭包(closure)又叫匿名函数,即没有指定名称的函数。如下:
<?php $f = function() { echo "this is closure" . PHP_EOL; };//带了结束符 $f();
需要注意,匿名函数后面的分号是不能省去的。可以把匿名函数看成一个特殊的变量,既然是变量,那么自然就可以作为形参,如下:
<?php $f = function() { echo "this is closure" . PHP_EOL; }; function doSomethings($f) { $f(); } doSomethings($f); doSomethings(function(){ echo "hello world from dq" . PHP_EOL; });
执行效果如下:
二、闭包传参
闭包的作用:在PHP里面,闭包的作用也是使变量持久化,即保存当前作用域里面的一些变量。当然我们知道,PHP的函数是没法直接访问当前作用域定义的变量,如下:
$str = 'hello'; f1(); function f1(){ var_dump($str); }
上诉代码,将会报错:
匿名函数,也是函数,自然也没法直接读取当前作用域的变量,但是我们可以利用PHP针对闭包提供的关键字use,将当前作用域的变量,传递到函数中。案例如下:
<?php $f1 = function() use($str){ echo "this is closure of f1;str=", $str , PHP_EOL; }; $str = 'hello'; $f2 = function() use($str){ echo "this is closure of f2 ;str=", $str , PHP_EOL; }; $f1(); $f2();
执行结果如下:
备注:需要注意,使用use往匿名函数里面传递变量的时候,变量必须在匿名函数之前定义,否则报错,提示变量未定义。
既然闭包,可以保存当前作用域里面的变量,是否可以修改当前作用域里面的变量呢?
如下,代码:
$str = 'hello'; $f2 = function() use($str){ echo "func init ,str=", $str , PHP_EOL; $str .= ' dqs'; echo "func change , str=", $str , PHP_EOL; }; echo "outer , str=", $str , PHP_EOL; $f2(); echo "outer , str=", $str , PHP_EOL;
执行效果如下:
当我在闭包中,修改传递过来的变量时候,并不会影响当前作用域里面的变量。这是因为,use传递过来的仅仅是当前变量的一个副本。
我们可以在形参前面加一个 & 符号,强制传递引用即可,如下:
$str = 'hello'; $f2 = function() use(&$str){ echo "func init ,str=", $str , PHP_EOL; $str .= ' dqs'; echo "func change , str=", $str , PHP_EOL; }; echo "outer , str=", $str , PHP_EOL; $f2(); echo "outer , str=", $str , PHP_EOL;
执行效果:
上面说了,通过关键字use,匿名函数获得的仅仅是一个副本,那下面是否会改变作用域中变量的值呢?
class Student{ public $name = 'dq'; private $age = 18; public function __construct($name) { $this->name = $name; } public function showName() { echo "my name is ", $this->name, PHP_EOL; } public function setName($name) { $this->name = $name; } } $dq = new Student('dqs'); $dq->showName(); $f2 = function() use($dq){ $dq->setName('dequan'); }; $f2(); $dq->showName();
执行结果如下:
上次看一个论坛上面,就有小伙伴问这个问题,既然是传值,为什么还会改变外部的值呢?
这是因为$dq变量是一个对象,可以这么理解,变量值是指向一个对象的地址,复制的时候,是把地址复制过去,故可以修改。这也是符合php变量的赋值的,如下:
$dq = new Student('dqs'); $dq->showName(); $dq2 = clone $dq; $dq2->setName('dq2'); //$dq2的改变,不影响$dq1 $dq->showName(); $dq3 = $dq; $dq3->setName('dq3');//$dq3的改变,影响$dq1 $dq->showName();
输出结果:
三、闭包类Closure的使用
为了更好的使用闭包,PHP里面定义了闭包类(Closure),所有的匿名函数,都是该闭包类(Closure)的实例。我们可以通过以下程序去验证:
<?php $f = function() { echo "this is closure" , PHP_EOL; }; echo gettype($f), PHP_EOL; echo $f instanceof Closure, PHP_EOL;
执行结果如下:
1、闭包中的$this
我们习惯将PHP里面的闭包叫做匿名函数,其实从本质上来说,它是一个对象,那为什么它可以像函数一样调用呢?这就要涉及到PHP中魔术方法__invoke()。在PHP中,任何对象都可以函数的方式调用。当我们以函数的方式,调用对象的时候,就触发__invoke,如下:
class Student{ public $name = 'dq'; private $age = 18; public function __construct($name) { $this->name = $name; } public function __invoke($args = ''){ echo $args, PHP_EOL; } } $s = new Student('dequan'); $s("测试啊");
但是,我们需要注意的是调用匿名函数的过程,与__invoke函数是没有关系的。
既然闭包是一个对象,那么它就拥有对象的一些特性,比如通过clone 进行复制、通过get_class_methods获取包含的所有方法等,但是它也是一个特殊的对象。首先不能够通过 Closure 类的构造函数去实例化,其次闭包对象中的$this也是有一定的区别。如下:
<?php $f = function($val) { var_dump($this); echo $val , PHP_EOL; }; $f('hello');
直接在闭包中使用$this,肯定会报错PHP Fatal error: Uncaught Error: Using $this when not in object context in
因为关于$this的定义,手册上是这么说的:
当一个方法在类定义内部被调用时,有一个可用的伪变量 $this。$this 是一个到主叫对象的引用(通常是该方法所从属的对象,但如果是从第二个对象静态调用时也可能是另一个对象)。
上面的闭包,不从属任何对象,所以会报错。关于$this更多的内容,可以参考官方手册。
要想在闭包中使用$this,我们需要将闭包绑定到指定的对象和类作用域。就得了解以下方法:
Closure::bind 复制一个闭包,绑定指定的$this对象和类作用域。
Closure::bindTo 复制当前闭包对象,绑定指定的$this对象和类作用域。
使用Closure::bind绑定$this
函数定义如下:
public static Closure Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] )
参数 说明 closure
需要绑定的匿名函数。 newthis 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包。
newscope 想要绑定给闭包的类作用域,或者 'static' 表示不改变。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。 The class scope to which associate the closure is to be associated, or 'static' to keep the current one. If an object is given, the type of the object will be used instead. This determines the visibility of protected and private methods of the bound object. 返回值:返回一个新的 Closure 对象 或者在失败时返回 FALSE
案例如下:
class Student{ public $name = 'dq'; private $age = 18; public function __construct($name) { $this->name = $name; } } $trace = function () { if (isset($this)) { echo '$this is defined (', get_class($this), ')', PHP_EOL; } else { echo '$this is not defined', PHP_EOL; } }; //未绑定$this $trace(); $s1 = new Student('dq'); //所谓的绑定,即返回一个新的闭包 $trace2 = Closure::bind($trace, $s1, 'Student'); $trace2();//此时闭包$trace2中,可以使用$this了
上面案例中$this代表的是$s1,输出结果:
使用Closure::bindTo绑定$this
函数的定义如下:
public Closure Closure::bindTo ( object $newthis [, mixed $newscope = 'static' ] )
参数 说明 newthis 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包。
newscope 想要绑定给闭包的类作用域,或者 'static' 表示不改变。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。 The class scope to which associate the closure is to be associated, or 'static' to keep the current one. If an object is given, the type of the object will be used instead. This determines the visibility of protected and private methods of the bound object. 返回值:返回一个新的 Closure 对象 或者在失败时返回 FALSE
案例如下(类和闭包定义同上面案例):
//未绑定$this $trace(); //绑定$this $s1 = new Student('dq'); $trace2 = $trace->bindTo($s1); $trace2();//此时$trace2中$this为$s1
输出结果如下: