关于一篇文章引发的匿名函数的思考

写在前面

闲来无事在浏览Laravel 底层分析这篇文章的时候,发现自己对匿名函数只是停留在表面上的理解,然鹅,作为一名“优秀”的?‍?‍,发自内心的好奇心让我去好好把玩了下匿名函数这个东西。

匿名函数

匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值,也可以作为函数的返回值。

示例1:

//声明一个匿名函数,并调用
<?php
$say = function() {
    echo 'hello';    //声明一个匿名函数,并赋值给$say这个变量,注意函数声明后面的‘;’
};
$say();    //变量加上()就可以调用匿名函数,输出“hello”

示例2:

//声明一个匿名函数,并作为回调函数(callback)参数的值
$say = function($name) {
    echo 'hello '.$name;
};
call_user_func($say,'dog');    //call_user_func调用参数1的回调函数,并将参数2当成回调函数的参数,输出“hello dog” 

匿名函数 和 闭包类(Closure)的关系

定义一个闭包函数,会产生一个闭包类(Closure)的对象。

Closure 类主要方法:

Closure {
/* 方法 */
    __construct ( void )
    public static bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] ) : Closure
    public bindTo ( object $newthis [, mixed $newscope = 'static' ] ) : Closure
}
  • Closure::construct — 用于禁止实例化的构造函数

    这个方法仅用于禁止实例化一个 Closure 类的对象。

  • Closure::bind — 用来复制参数1给定的闭包,并指定闭包的 this 指针指向参数2,闭包的类的作用域为 参数3

    参数

    • closure
      需要绑定的匿名函数。
    • newthis
      需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包。
    • newscope
      想要绑定给闭包的类作用域,或者 'static' 表示不改变。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性
  • Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。

    用法和bind差不多,这里就不多赘述

示例1:

//声明一个狗对象
class Dog
{
    public $name = '旺财';
}

//声明一个猫对象
class Cat
{
    public $name = '喵喵';
}

//声明一个匿名函数,用来输出名字
$sayName = function() {
    echo $this->name;
};

//用bind赋值上面那个匿名函数,并指定this指向狗对象,返回新的闭包函数
$sayDogName = Closure::bind($sayName, new Dog);

//调用新的闭包函数,输出 “旺财”
$sayDogName();  

//用bind赋值上面那个匿名函数,并指定this指向猫对象,返回新的闭包函数
$sayCatName = Closure::bind($sayName, new Cat);

//调用新的闭包函数,输出 “喵喵”
$sayCatName();

//用bind赋值上面那个匿名函数,this指向null
$sayNullName = Closure::bind($sayName, null);

//调用新的闭包函数,由于this指向null,报错 “Using $this when not in object context”
$sayNullName();

上面这个例子阐明了参数2的作用,那么参数3呢,往下瞅

示例2:

//还是声明一条狗狗,不过狗狗的名字变成了私有,只有主人才知道狗狗叫旺财
class Dog
{
    private $name = '旺财';
}

//声明匿名函数
$sayName = function() {
    echo $this->name;
};

//用bind赋值上面那个匿名函数,并指定this指向狗对象,返回新的闭包函数
$sayDogName = Closure::bind($sayName, new Dog);

//调用新的闭包函数,报错 “Cannot access private property Dog::$name”
$sayDogName();  

原因:
我们将 this 指向了 Dog 对象,但是我们并没有把类的作用域指向 Dog,所以,闭包函数 sayDogName 无法调用 Dog 的私有属性 name。接下来,我们来指定下类的作用域。

示例3:

//指定类的作用域为 Dog 对象
$sayDogName = Closure::bind($sayName, new Dog, 'Dog');

//输出 “旺财”
$sayDogName();  

匿名函数 和 Use

use是用来是连接闭包和外界变量。

示例1:

$a = 1;

$b = function () use($a) {
    $a += 10;
    echo $a.PHP_EOL;    //输出“11”
};

$b();

echo $a;    //输出1

那么如果我们传递的变量是个对象呢?

class Dog
{
    public $name = '旺财';
}

$aDog = new Dog;

$closureFunc = function() use($aDog) {
    $aDog->name = '旺财他二爷';
};

$closureFunc();

echo $aDog->name;    //输出“旺财他二爷”

咦,不是说闭包函数里面的操作不会改变闭包函数外的变量吗?咋回事??
其实,对象变量属于引用类型的数据,use在复制对象变量的时候,虽然操作的是另外一个变量,但是这个变量其指向的对象地址并没有发生改变,所以当我们对复制的对象变量进行操作的时候,对象的属性也会发生相应的改变,同时会影响到其他也指向这个对象的变量。

举个简单?做下对比:

$a = 1;

$b = $a;

$b = 2;

echo $a;    //输出1

class Dog
{
    public $name = '旺财';
}

$a = new Dog;

$b = $a;    

$b->name = '旺财的儿子';

echo $a->name;    //输出“旺财的儿子”

同理,如果我们需要同时改变闭包外普通变量$a的值,那么我们就需要引用传值&,如下:

$a = 1;

$b = function () use(&$a) {
    $a += 10;
    echo $a.PHP_EOL;    //输出“11”
};

$b();

echo $a;    //输出11

最后关于 Use 传值的一点小疑问

匿名函数也可以通过函数的参数传递变量进来,如下

$a = 1;

$b = function ($a)  {
    $a += 10;
    echo $a.PHP_EOL;    //输出“11”
};

$b($a);

echo $a;    //输出1

那么这种传值方式和Use有何区别?希望看到的小伙伴可以在评论区帮小弟解答一下,感激不尽!!

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 3

自动执行

 (function() {
    echo 'hello';    //声明一个匿名函数,并赋值给$say这个变量,注意函数声明后面的‘;’
})();

传参和use没看出区别,只是use自由点

2周前 评论
三木闲僧: 嗯嗯,立即执行 2周前

我觉得关键是使用的时候吧,匿名函数主要就是用来做回调,但执行回调的函数又不知道你的匿名函数需要几个参数,可以参考查询语句里 when 回调的用法,when 执行回调函数只会传两个参数,但我们常常需要其它变量来拼接查询语句,这时就需要使用 use 了

 public function when($value, callable $callback, callable $default = null)
    {
        if ($value) {
            return $callback($this, $value);
        } elseif ($default) {
            return $default($this, $value);
        }

        return $this;
    }
1周前 评论

既然匿名函数最常用处是回调,那么我们“调用”时候肯定发生在未来,那么很好理解了,use可以用未来的变量,而参数没得选择

1周前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!