在PHP的编程中,我们经常会遇到或者使用闭包,也叫做匿名函数。这个特性从PHP5.3开始支持,匿名函数可用于动态创建函数,并保存到一个变量中,以及其他巧妙的用法,下面我们来了解一下闭包。
最常见的匿名函数
<?php
$func = function(){
echo('Hello world!!');
};//这里必须要有;结尾
$func();
?>
可以看到,将函数保存为变量func,通过变量名()
来调用函数。以上代码等同于:
<?php
function func(){
echo('Hello world!!');
};//这里必须要有;结尾
func();
?>
看上去没什么特别的,那么我们继续研究
<?php
function operate($operator){
if($operator == "-"){
return function($a,$b){
return $a-$b;
};
}else{
return function($a,$b){
return $a+$b;
};
}
}
$subtraction = operate("-");
echo $subtraction(4,3);//输出1
$addition = operate("+");
echo $addition(1,2);//输出3
?>
以上代码通过给函数operate传入不同的参数,从而返回不同的匿名函数,用变量来保存返回的匿名函数,再来调用。其巧妙之处在于动态的返回不同的函数。
进阶匿名函数
<?php
class Di{
private $_factory;
public function set($id,$value){
$this->_factory[$id] = $value;
}
public function get($id){
$value = $this->_factory[$id];
return $value();//此处调用传入的匿名方法生成对象
}
}
class User{
private $_username;
function __construct($username="") {
$this->_username = $username;
}
function getUserName(){
return $this->_username;
}
}
//从这里开始看
$di = new Di();
$di->set("zhangsan",function(){
return new User('张三');
});
$di->set("lisi",function(){
return new User("李四");
});
echo $di->get("zhangsan")->getUserName();
echo $di->get("lisi")->getUserName();
?>
代码中的Di容器用来保存返回对象实例的方法,使用di容器用set()注册了两个这样的方法,通过get()方法获取方法。
我们看到$di->set()的时候,使用了匿名函数,我们预先注册了zhangsan和lisi两个服务,这两个服务都是User类的实例,在$di->set的时候实际上并没有实例化,而是在$di->get()的时候才执行了匿名函数并将对象返回,这就实现了按需实例化,不用则不实例化,提高效率(这点很重要)。
闭包特性
闭包的特性:
①.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
②.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
优点:
① 减少全局变量。
② 减少传递函数的参数量
③ 封装
缺点:
使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等。
经常写js的同学应该知道js的闭包特性,简单的说就是在一个函数里可以定义子函数,父函数中的变量在子函数中可以直接使用,那么PHP的闭包有什么区别。
<?php
function func1($a,$b){
return function() use ($a,$b){
echo $a,$b;
};
}
$a = func1("a",'dd');
$a();//输出add
?>
将外层变量传进内层匿名函数时,使用use (变量)的方式。
闭包的常见功能
减少foreach的循环的代码
<?php
// 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。
// 其中有一个方法用来计算购物车中所有商品的总价格。该方法使用了一个closure作为回调函数。
class Cart
{
const PRICE_BUTTER = 1.00;//3个产品的价格
const PRICE_MILK = 3.00;
const PRICE_EGGS = 6.95;
protected $products =array();
public function add($product,$quantity)
{
$this->products[$product] = $quantity;
}
public function getQuantity($product)
{
return isset($this->products[$product]) ? $this->products[$product] :
FALSE;
}
public function getTotal($tax)
{
$total = 0.00;
$callback =
function ($quantity,$product)use ($tax, &$total)//传地址进来
{
$pricePerItem = constant(__CLASS__ ."::PRICE_" .
strtoupper($product));//获取头部定义的3个常量
$total += ($pricePerItem *$quantity) * ($tax + 1.0);
};
array_walk($this->products,$callback);//array_walk()对数组中的每个元素应用用户自定义函数,一般为函数赋值2个参数,分别为数组的键值。
return round($total, 2);//round() 函数对浮点数进行四舍五入。
}
}
$my_cart =new Cart;
// 往购物车里添加条目
$my_cart->add('butter', 1);//产品名及个数
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);
// 打出出总价格,其中有 5% 的销售税.
print $my_cart->getTotal(0.05) . "\n";
// The result is 54.29
?>
可以看到,通过array_walk函数对匿名函数的调用实现了循环计算价格,如果不用匿名函数的话不好用array_walk,因为只为被调用函数赋值2个参数,而计算价格所需的参数较多。
减少函数的参数
<?php
function html ($code ,$id="",$class=""){
if ($id !=="")$id =" id = \"$id\"" ;
$class = ($class !=="")?" class =\"$class\"":">";
$open ="<$code$id$class";
$close ="</$code>";
return function ($inner ="")use ($open,$close){
echo "$open$inner$close";
};
}
html("java","id","class")("hahahh")
?>
结果为:
<java id = "id" class ="class"hahahh</java>
如果是使用平时的方法,我们会把inner放到html函数参数中,说实话我不认为这有什么意义,等于将参数拆开赋值而已,并将一个函数拆成2个。或者这样:
$code=html("java","id","class");
$code("hahah");
看个人需求吧。
代替递归函数
<?php
$fib =function($n)use(&$fib) {//必须用&,变量没有定义
if($n == 0 || $n == 1) return 1;
return $fib($n - 1) + $fib($n - 2);
};
echo $fib(5) . "\n";// 2
function tigui($n){
if($n == 0 || $n == 1) return 1;
return tigui($n - 1) + tigui($n - 2);
}
echo tigui(5);
关于延迟绑定
如果你需要延迟绑定use里面的变量,你就需要使用引用(&),否则在定义的时候就会做一份拷贝放到use中
<?php
$result = 0;
$two =function()use ($result)
{ var_dump($result); };
$three =function()use (&$result)
{ var_dump($result); };
$result++;
$two(); // outputs int(0): $result was copied
$three(); // outputs int(1)
two方法拷贝一份为0的数据,所以结果为0
运行完result++之后,result为1,再调用three方法绑定,结果为1