正文
匿名函数和闭包并没有什么直接关系
匿名函数
什么是匿名函数?
与匿名函数相对应的是具名函数,具名函数非常简单:function myFn(){}
,这就是个具名函数这个函数的name是myFn。
PHP声明一个匿名函数是这样:
$func = function() {
}; //带结束符
可以看到,匿名函数因为没有名字,如果要使用它,需要将其返回给一个变量。 匿名函数也像普通函数一样可以声明参数,调用方法也相同:
$func = function($param) {
echo $param;
};
$func('some string');
//输出:
//some string
闭包
闭包是怎么定义的,该如何理解?
闭包本身定义比较抽象,MDN官方上解释是: A closure is the combination of a function and the lexical environment within which that function was declared.
中文解释是:闭包是一个函数和该函数被定义时的词法环境的组合。
按照闭包起的作用来理解它:就是能在一个函数外部执行这个函数内部定义的方法,并访问这个函数内部定义的变量。
闭包跟函数是否匿名没有直接关系,匿名函数和具名函数都可以创建闭包。
function box(){
var a = 10;
function inner(){
return a;
}
return inner;
}
var outer = box();
console.log(outer());//10
//第一步直把内部inner这个具名函数改为匿名函数并直接return, 结果同样是10
function box(){
var a = 10;
return function(){
console.log(a) ;
}
}
var outer = box();
outer();//10
//第二步把外部var outer = box()改成立即执行的匿名函数
var outer = (function(){
var a=10;
return function(){
console.log(a);
}
})();
//outer 作为立即执行匿名函数执行结果的一个接收,这个执行结果是闭包,outer等于这个闭包。
//执行outer就相当于执行了匿名函数内部return的闭包函数
//这个闭包函数可以访问到匿名函数内部的私有变量a,所以打印出10
outer();//10
实现闭包
将匿名函数在普通函数中当做参数传入,也可以被返回。这就实现了一个简单的闭包。
//例一
//在函数里定义一个匿名函数,并且调用它
function printStr() {
$func = function( $str ) {
echo $str;
};
$func( 'some string' );
}
printStr();
//例二
//在函数中把匿名函数返回,并且调用它
function getPrintStrFunc() {
$func = function( $str ) {
echo $str;
};
return $func;
}
$printStrFunc = getPrintStrFunc();
$printStrFunc( 'some string' );
//例三
//把匿名函数当做参数传递,并且调用它
function callFunc( $func ) {
$func( 'some string' );
}
$printStrFunc = function( $str ) {
echo $str;
};
callFunc( $printStrFunc );
//也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉
callFunc( function( $str ) {
echo $str;
} );
USE 关键字
连接闭包和外界变量的关键字:USE,use意思是连接闭包和外界变量。
闭包可以保存所在代码块上下文的一些变量和值。PHP在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用use关键字。
换一个例子看看:
function getMoney() {
$rmb = 1;
$dollar = 6;
$func = function() use ( $rmb ) {
echo $rmb;
echo $dollar;
};
$func();
}
getMoney();
//输出:
//1
//报错,找不到dorllar变量
可以看到,dollar没有在use关键字中声明,在这个匿名函数里也就不能获取到它,所以开发中要注意这个问题。
在匿名函数中不能改变上下文的变量,use所引用的也只不过是变量的一个副本而已:
function getMoney() {
$rmb = 1;
$func = function() use ( $rmb ) {
echo $rmb;
//把$rmb的值加1
$rmb++;
};
$func();
echo $rmb;
}
getMoney();
//输出:
//1
//1
要完全引用变量,而不是复制,需要按地址&引用:
function getMoney() {
$rmb = 1;
$func = function() use ( &$rmb ) {
echo $rmb;
//把$rmb的值加1
$rmb++;
};
$func();
echo $rmb;
}
getMoney();
//输出:
//1
//2
如果将匿名函数返回给外界,匿名函数会保存use所引用的变量,而外界则不能得到这些变量:
function getMoneyFunc() {
$rmb = 1;
$func = function() use ( &$rmb ) {
echo $rmb;
//把$rmb的值加1
$rmb++;
};
return $func;
}
$getMoney = getMoneyFunc();
$getMoney();
$getMoney();
$getMoney();
//输出:
//1
//2
//3
闭包的几个作用
减少foreach的循环的代码
<?php
// 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。
// 其中有一个方法用来计算购物车中所有商品的总价格。该方法使用了一个closure作为回调函数。
class Cart
{
const PRICE_BUTTER = 1.00;
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));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};
array_walk($this->products, $callback);
return round($total, 2);;
}
}
$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
减少函数的参数
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){
return "$open$inner$close";
};
}
如果是使用平时的方法,我们会把inner放到html函数参数中,这样不管是代码阅读还是使用都不如使用闭包
解除递归函数
$fib = function($n) use(&$fib) {
if($n == 0 || $n == 1) return 1;
return $fib($n - 1) + $fib($n - 2);
};
echo $fib(2) . "\n"; // 2
$lie = $fib;
$fib = function(){die('error');};//rewrite $fib variable
echo $lie(5); // error because $fib is referenced by closure
$fib = function(){echo 3;};
echo $lie(1); // 1
echo $lie(2); // 330
echo $lie(3); // 330
注意上题中的use使用了&,这里不使用&会出现错误fib(n-1)
是找不到function的(前面没有定义fib的类型)
所以想使用闭包解除循环函数的时候就需要使用这样的形式:
<?php
$recursive = function () use (&$recursive){
// The function is now available as $recursive
}
关于延迟绑定
如果你需要延迟绑定use里面的变量,你就需要使用引用,否则在定义的时候就会做一份拷贝放到use中:
<?php
$result = 0;
$one = function()
{
var_dump($result);
};
$two = function() use ($result)
{
var_dump($result);
};
$three = function() use (&$result)
{
var_dump($result);
};
$result++;
$one(); // outputs NULL: $result is not in scope
$two(); // outputs int(0): $result was copied
$three(); // outputs int(1)
$two(); // outputs int(0):
使用引用和不使用引用就代表了是调用时赋值,还是申明时候赋值。
Closure类
Closure类原型:
/**
* Class used to represent anonymous functions.
* <p>Anonymous functions, implemented in PHP 5.3, yield objects of this type.
* This fact used to be considered an implementation detail, but it can now be relied upon.
* Starting with PHP 5.4, this class has methods that allow further control of the anonymous function after it has been created.
* <p>Besides the methods listed here, this class also has an __invoke method.
* This is for consistency with other classes that implement calling magic, as this method is not used for calling the function.
* @link https://secure.php.net/manual/en/class.closure.php
*/
final class Closure {
/**
* This method exists only to disallow instantiation of the Closure class.
* Objects of this class are created in the fashion described on the anonymous functions page.
* @link https://secure.php.net/manual/en/closure.construct.php
*/
private function __construct() { }
/**
* This is for consistency with other classes that implement calling magic,
* as this method is not used for calling the function.
* @param mixed ...$_ [optional]
* @return mixed
* @link https://secure.php.net/manual/en/class.closure.php
*/
public function __invoke(...$_) { }
/**
* Duplicates the closure with a new bound object and class scope
* @link https://secure.php.net/manual/en/closure.bindto.php
* @param object|null $newThis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound.
* @param mixed $newScope 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.
* @return Closure|false Returns the newly created Closure object or FALSE on failure
*/
function bindTo($newThis, $newScope = 'static') { }
/**
* This method is a static version of Closure::bindTo().
* See the documentation of that method for more information.
* @link https://secure.php.net/manual/en/closure.bind.php
* @param Closure $closure The anonymous functions to bind.
* @param object|null $newThis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound.
* @param mixed $newScope 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.
* @return Closure|false Returns the newly created Closure object or FALSE on failure
*/
static function bind(Closure $closure, $newThis, $newScope = 'static') { }
/**
* Temporarily binds the closure to newthis, and calls it with any given parameters.
* @link https://php.net/manual/en/closure.call.php
* @param object $newThis The object to bind the closure to for the duration of the call.
* @param mixed $args [optional] Zero or more parameters, which will be given as parameters to the closure.
* @return mixed
* @since 7.0
*/
function call ($newThis, ...$args) {}
/**
* @param callable $callback
* @return Closure
* @since 7.1
*/
public static function fromCallable (callable $callback) {}
}
Closure::__construct — 用于禁止实例化的构造函数。
Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
Closure::bind
Closure::bind是Closure::bindTo的静态版本,其说明如下:
public static Closure bind(Closure $closure, $newThis, $newScope = 'static') { }
closure表示需要绑定的闭包对象。
newthis表示需要绑定到闭包对象的对象,或者NULL创建未绑定的闭包。
newscope表示想要绑定给闭包的类作用域,可以传入类名或类的示例,默认值是 ‘static’, 表示不改变。
该方法成功时返回一个新的 Closure 对象,失败时返回FALSE。
例子说明:
<?php
/**
* 复制一个闭包,绑定指定的$this对象和类作用域。
*
* @author 疯狂老司机
*/
class Animal
{
private static $cat = "cat";
private $dog = "dog";
public $pig = "pig";
}
/*
* 获取Animal类静态私有成员属性
*/
$cat = static function() {
return Animal::$cat;
};
/*
* 获取Animal实例私有成员属性
*/
$dog = function() {
return $this->dog;
};
/*
* 获取Animal实例公有成员属性
*/
$pig = function() {
return $this->pig;
};
$bindCat = Closure::bind($cat, null, new Animal());// 给闭包绑定了Animal实例的作用域,但未给闭包绑定$this对象
$bindDog = Closure::bind($dog, new Animal(), 'Animal');// 给闭包绑定了Animal类的作用域,同时将Animal实例对象作为$this对象绑定给闭包
$bindPig = Closure::bind($pig, new Animal());// 将Animal实例对象作为$this对象绑定给闭包,保留闭包原有作用域
echo $bindCat(),'<br>';// 根据绑定规则,允许闭包通过作用域限定操作符获取Animal类静态私有成员属性
echo $bindDog(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象(Animal实例对象)获取Animal实例私有成员属性
echo $bindPig(),'<br>';// 根据绑定规则,允许闭包通过绑定的$this对象获取Animal实例公有成员属性
输出:
cat
dog
pig
Closure::bindTo说明
Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域,其说明如下:
public Closure bindTo($newThis, $newScope = 'static') { }
newthis表示绑定给闭包对象的一个对象,或者NULL来取消绑定。
newscope表示关联到闭包对象的类作用域,可以传入类名或类的示例,默认值是 ‘static’, 表示不改变。
该方法创建并返回一个闭包对象,它与当前对象绑定了同样变量,但可以绑定不同的对象,也可以绑定新的类作用域。 绑定的对象决定了返回的闭包对象中的$this的取值,类作用域决定返回的闭包对象能够调用哪些方法, 也就是说,此时$this可以调用的方法,与newscope类作用域相同。
例子1
Template.php 模板类
<?php
/**
* 模板类,用于渲染输出
*/
class Template{
/**
* 渲染方法
*
* @access public
* @param obj 信息类
* @param string 模板文件名
*/
public function render($context, $tpl){
$closure = function($tpl){
ob_start();
include $tpl;
return ob_end_flush();
};
$closure = $closure->bindTo($context, $context);
$closure($tpl);
}
}
Article.php 信息类
<?php
/**
* 文章信息类
*/
class Article{
private $title = "这是文章标题";
private $content = "这是文章内容";
}
tpl.php 模板文件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<h1><?php echo $this->title;?></h1>
<p><?php echo $this->content;?></p>
</body>
</html>
使用调用:
<?php
function __autoload($class) {
require_once "$class.php";
}
$template = new Template;
$template->render(new Article, 'tpl.php');
?>
例子2
<?php
/**
* 给类动态添加新方法
*/
trait DynamicTrait {
/**
* 自动调用类中存在的方法
*/
public function __call($name, $args) {
if(is_callable($this->$name)){
return call_user_func($this->$name, $args);
}else{
throw new \RuntimeException("Method {$name} does not exist");
}
}
/**
* 添加方法
*/
public function __set($name, $value) {
$this->$name = is_callable($value)?
$value->bindTo($this, $this):
$value;
}
}
/**
* 只带属性不带方法动物类
*/
class Animal {
use DynamicTrait;
private $dog = 'dog';
}
$animal = new Animal;
// 往动物类实例中添加一个方法获取实例的私有属性$dog
$animal->getdog = function() {
return $this->dog;
};
echo $animal->getdog();
?>
输出:
dog
例子3
<?php
/**
* 一个基本的购物车,包括一些已经添加的商品和每种商品的数量
*/
class Cart {
// 定义商品价格
const PRICE_BUTTER = 1.00;
const PRICE_MILK = 3.33;
const PRICE_EGGS = 8.88;
protected $products = array();
/**
* 添加商品和数量
*
* @access public
* @param string 商品名称
* @param string 商品数量
*/
public function add($item, $quantity) {
$this->products[$item] = $quantity;
}
/**
* 获取单项商品数量
*
* @access public
* @param string 商品名称
*/
public function getQuantity($item) {
return isset($this->products[$item]) ? $this->products[$item] : FALSE;
}
/**
* 获取总价
*
* @access public
* @param string 税率
*/
public function getTotal($tax) {
$total = 0.00;
$callback = function ($quantity, $item) use ($tax, &$total) {
$pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($item));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};
array_walk($this->products, $callback);
return round($total, 2);;
}
}
$my_cart = new Cart;
// 往购物车里添加商品及对应数量
$my_cart->add('butter', 10);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 12);
// 打出出总价格,其中有 5% 的销售税.
echo $my_cart->getTotal(0.05);
总结:合理使用闭包能使代码更加简洁和精炼。
参考资料
PHP 手册 语言参考 函数 匿名函数 https://www.php.net/manual/zh/functions.anonymous.php
PHP闭包(Closure)初探 https://www.cnblogs.com/melonblog/archive/2013/05/01/3052611.html
PHP的闭包 https://www.cnblogs.com/yjf512/archive/2012/10/29/2744702.html
PHP Closure类详解 https://blog.csdn.net/wuxing26jiayou/article/details/51067190
浅谈匿名函数和闭包 https://www.jianshu.com/p/0a3150afb7ed