闭包匿名函数,还在傻傻搞不清楚吗?

闭包

还在正月里,首先祝大家新年快乐!本文是从PHP角度来看闭包与匿名函数。

闭包基础

闭包长啥样

以下是最常见的闭包形式,其他方法创建的闭包在此就不做说明了


$helloWorld = function() {
    return 'hello world';
};

var_dump($helloWorld);

/*
返回结果如下:
object(Closure)#1 (0) {
}
*/

定义闭包

闭包 = 匿名函数 = 实例 = 对象。 类代码大概如下:


// 闭包类
class Closure{
    // 禁止实例化
    private __construct()
    {

    }

    // 复制一个闭包,绑定指定的 $newThis 对象和类的作用域
    public static function bind(Closure $closure, object $newThis, mixed $newscope = 'static') :Closure
    {
        // TO-DO
    }

    // 复制当前闭包对象, 绑定指定的 $newThis 对象和类的作用域 
    public function bindTo(object $newThis, mixed $newscope = 'static') :Closure
    {
        // TO-DO
    }

    // 魔术方法,无作用
    public function __invoke()
    {
        // TO-DO
    }
}
方法 说明
__construct 用于禁止实例化的构造方法
bind 复制一个闭包,绑定指定的$this对象和类作用域
bindTo 复制当前闭包,绑定指定的$this对象和类作用域

真实应用闭包

例如我们代码中需要将数组值转化int类型

$params = ['10', '20', '30', '40'];

$newArray = array_map(function($item) {
    return intval($item);
}, $params);

var_dump($newArray);

/*
返回结果如下:
array(4) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  int(30)
  [3]=>
  int(40)
}

*/

闭包高级应用

Closure::bind

通俗点理解就是让我们的闭包代码块的作用域在某一类中或者对象中。使得闭包中的 $this-> self:: 类名::能指定到对象或者是类

代码块


/**
 * Closure class a method
 * 复制一个闭包,绑定指定的 $newThis对象和类的作用域
 *
 * @param Closure $closure
 * @param object $newThis
 * @param mixed $newscope
 * @return \Closure
 */
public static function bind(Closure $closure, object $newThis, mixed $newscope = 'static') :Closure
{
    // TO-DO
}

参数说明

PHP 手册 中有以下几句可加深我们理解:

创建并返回一个 匿名函数, 它与当前对象的函数体相同、绑定了同样变量,但可以绑定不同的对象,也可以绑定新的类作用域。

“绑定的对象”决定了函数体中的 $this 的取值,“类作用域”代表一个类型、决定在这个匿名函数中能够调用哪些 私有 和 保护 的方法。 也就是说,此时 $this 可以调用的方法,与 newscope 类的成员函数是相同的。

静态闭包不能有绑定的对象( newthis 参数的值应该设为 NULL)不过仍然可以用 bubdTo 方法来改变它们的类作用域。


参数 说明
$closure 表示闭包函数
$newThis 闭包中 $this 所指的对象
$newscope 我们闭包中需要操作属性等所属类的类型名

代码检验真理

class Order
{
    private $orderId = '001';

    private static $defaultMoney = '100';

    public $num = 1;
}

$getOrderId = function() {
    return $this->orderId;
};

$getNum = function() {
    return $this->num;
};

$getDefaultMoney = static function() {
    return Order::$defaultMoney;
};
1. Closure::bind($closure,null, 'Order')

给闭包绑定了Order类的作用域,但未绑定闭包$this对象 (Order::class == new Order() == 'Order')

$getDefaultMoney1 = Closure::bind($getDefaultMoney, null, Order::class);
// 输出:100

$getOrderId1 = Closure::bind($getOrderId, null, Order::class);
// 输出:PHP Fatal error:  Uncaught Error: Using $this when not in object context

$getNum1 = Closure::bind($getNum, null, Order::class);
// 输出:PHP Fatal error:  Uncaught Error: Using $this when not in object context
2. Closure::bind($closure,$object, 'Order')

给闭包绑定了Order类的作用域,将Order实例绑定闭包$this对象

$getDefaultMoney2 = Closure::bind($getDefaultMoney, new Order(), Order::class);
// 输出:PHP Warning:  Cannot bind an instance to a static closure

$getOrderId2 = Closure::bind($getOrderId, new Order(), Order::class);
// 输出 string(3) "001"

$getNum2 = Closure::bind($getNum, new Order(), Order::class);
// 输出 int(1)
3. Closure::bind($closure,$object)

将Order实例对象作为$this对象绑定给闭包,保留闭包原有作用域

$getDefaultMoney3 = Closure::bind($getDefaultMoney, new Order());
// 输出 PHP Warning:  Cannot bind an instance to a static closure

$getOrderId3 = Closure::bind($getOrderId, new Order());
// 输出 Fatal error: Uncaught Error: Cannot access private property Order::$orderId

$getNum3 = Closure::bind($getNum, new Order());
// 输出 int(1)

结论

$newThis $newscope 结果
null Order::class 可调用 Order 类作用域私有(受保护)的静态属性
new Order() Order::class 可调用该对象的私有(受保护)的属性
new Order() 默认值 可调用类作用域公共的属性

$closure->bindTo

Closure::bind相同。一个是静态版,一个是非静态版。具体使用可以参考上面

$getOrderId1 = $getOrderId->bindTo(new Order(), Order::class);

小任务

利用闭包在不修改Order类的前提下增加 getOrderId 功能。最后实现的是如下两行代码调用方法。大家可以思考下

$order = new Order();
echo $order->getOrderId();

// 代码段
function getOrderId()
{
    return $this->orderId;
}

闭包 == 匿名函数

感谢@Wi1dcard补充: 在 PHP 内,由于匿名函数是通过闭包类实现的( Anonymous functions are implemented using the Closure class),因此多数人混淆了闭包技术和匿名函数;实际上,在其他语言内是完全不同的两个概念的。

参考:
http://php.net/manual/en/functions.anonymo...
https://stackoverflow.com/questions/491211...

闭包是一项「技术」或者说「功能」,能够捕获并存储当前当前上下文状态,以供后续使用。
匿名函数就只是一个「函数」,一个没有名字的函数而已。
在实际应用中,匿名函数通常伴随着使用闭包技术;但闭包并不一定只能用在匿名函数内。

相关链接

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 9

搭个车补充一下:在 PHP 内,由于匿名函数是通过闭包类实现的(Anonymous functions are implemented using the Closure class),因此多数人混淆了闭包技术和匿名函数;实际上,在其他语言内是完全不同的两个概念的。

参考:

闭包是一项「技术」或者说「功能」,能够捕获并存储当前当前上下文状态,以供后续使用。

匿名函数就只是一个「函数」,一个没有名字的函数而已。

在实际应用中,匿名函数通常伴随着使用闭包技术;但闭包并不一定只能用在匿名函数内。

5年前 评论

仿照PHP手册写的,请大家看下

/**
 * Class Order
 */
class Order
{
    use Meta;

    private $orderId = '001';

    private static $defaultMoney = '100';

    public $num = 1;
}

/**
 * Trait Meta
 */
trait Meta
{
    /**
     * 增加方法容器
     *
     * @var array
     */
    private $method = [];

    /**
     * 增加方法
     *
     * @param string $methodName
     * @param Closure $callBack
     * @throws Exception
     */
    public function addMethod($methodName, $callBack)
    {
        if (!is_callable($callBack)) {
            throw new Exception('该回调无法调用');
        }

        // 调用该方法
        $this->method[$methodName] = $callBack->bindTo($this, get_class());
        // 或者 $this->method[$methodName] = Closure::bind($callBack, $this,  get_class());
    }

    /**
     * 魔术方法调用
     * 
     * @param $methodName
     * @param array $args
     * @return mixed
     */
    public function __call($methodName, array $args)
    {
        if (isset($this->method[$methodName])) {
            return call_user_func_array($this->method[$methodName], $args);
        }
    }

}

// 客户端调用如下

$order = new Order();
$order->addMethod('getOrderId', function () {
    return "订单ID:{$this->orderId}";
});

echo $order->getOrderId();
5年前 评论

仿照PHP手册写的,请大家看下

/**
 * Class Order
 */
class Order
{
    use Meta;

    private $orderId = '001';

    private static $defaultMoney = '100';

    public $num = 1;
}

/**
 * Trait Meta
 */
trait Meta
{
    /**
     * 增加方法容器
     *
     * @var array
     */
    private $method = [];

    /**
     * 增加方法
     *
     * @param string $methodName
     * @param Closure $callBack
     * @throws Exception
     */
    public function addMethod($methodName, $callBack)
    {
        if (!is_callable($callBack)) {
            throw new Exception('该回调无法调用');
        }

        // 调用该方法
        $this->method[$methodName] = $callBack->bindTo($this, get_class());
        // 或者 $this->method[$methodName] = Closure::bind($callBack, $this,  get_class());
    }

    /**
     * 魔术方法调用
     * 
     * @param $methodName
     * @param array $args
     * @return mixed
     */
    public function __call($methodName, array $args)
    {
        if (isset($this->method[$methodName])) {
            return call_user_func_array($this->method[$methodName], $args);
        }
    }

}

// 客户端调用如下

$order = new Order();
$order->addMethod('getOrderId', function () {
    return "订单ID:{$this->orderId}";
});

echo $order->getOrderId();
5年前 评论

真棒!

5年前 评论

搭个车补充一下:在 PHP 内,由于匿名函数是通过闭包类实现的(Anonymous functions are implemented using the Closure class),因此多数人混淆了闭包技术和匿名函数;实际上,在其他语言内是完全不同的两个概念的。

参考:

闭包是一项「技术」或者说「功能」,能够捕获并存储当前当前上下文状态,以供后续使用。

匿名函数就只是一个「函数」,一个没有名字的函数而已。

在实际应用中,匿名函数通常伴随着使用闭包技术;但闭包并不一定只能用在匿名函数内。

5年前 评论

@Wi1dcard 多谢赐教。PHP手册中也写道了如此。我能否将你的见解补充到文章后面呢?

5年前 评论

@悲剧不上演 当然可以,开源社区,大家互相学习~

5年前 评论
kkk1

nice

5年前 评论
九霄道长

mark

2年前 评论
九霄道长

这么好的文章我居然一开始搜不到

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!