【PHP8入门指南】匿名函数与箭头函数

478 阅读1分钟

匿名函数

匿名函数,也叫闭包函数,通过临时创建一个没有指定名称的函数,我们可以更方便地处理我们的变量或者通过变量名的方式去使用我们的匿名函数。

匿名函数目前是通过Closure类来实现的。

例:

<?php
//一个闭包函数
$anymouse =  function() {
  return "这是一个闭包函数";
};

//一个有名函数
function anymouse() {
    return "这是一个有名函数";
}
echo $anymouse();
echo "\n";
echo anymouse();
echo "\n";
//打印闭包函数的类型
var_dump($anymouse);

运行结果:

由上我们看出,在调用上闭包的调用与有名函数的调用其实就差个$符号的区别;而通过打印闭包函数赋值的变量,可以看出闭包函数是一个closure对象。这是因为PHP 会自动把此种表达式转换成内置类 Closure 的对象实例。把一个 closure 对象赋值给一个变量的方式与普通变量赋值的语法是一样的,最后也要加上分号。

匿名函数除了变量赋值以外,还可以配合PHP内置的函数来使用。

例:

<?php
// 通过内置函数array_map对数组内的每个元素都执行一次闭包内的操作
// 并返回一个执行操作之后的新数组
$arr    = [1, 2, 3, 4, 5];
$newArr = array_map(function ($val) {
    // arr数组中的元素都乘以2
    return $val * 2;
}, $arr);
var_export($newArr);
echo "\n";
//原数组不变
var_export($arr);

运行结果:

匿名函数中使用变量

闭包可以从父作用域中继承变量。任何此类变量都应该用 use 语言结构传递进去。PHP 7.1 起,不能传入此类变量: superglobals、 $this 或者和参数重名。返回类型声明必须放在 use 子句的 后面 。

例:

<?php
// 在函数外声明变量str
$str  = "hello world";
$show = function ($val) use ($str) {
    return $val." ".$str;
};
// 通过传参方式传入参数
$name = "程序员菜菜";
echo $show($name);

运行结果:

同样,在有名函数中的引用传递在匿名函数中同样有效。

例:

<?php
// 在函数外声明变量str
$str  = "hello world";
$show = function () use (&$str) {
    $str .= " haha";
    return $str;
};
echo $show();
echo "\n";
echo $str;//通过引用传递后原本的变量的值改变了

运行结果:

通过在use子句后跟上冒号和类型,我们也可以实现跟有名函数那样的限定返回类型的效果。当然,传参时也一样。

例:

<?php
$a = 10;
$add = function (int $b) use ($a) : int {
    return $a + $b;
};
echo $add(2);
echo $add(3.222222);

运行结果:

从上可以看出,当传递的参数的类型不符合限定时,PHP会报警告并自动保留整数部分。

从 PHP 8.0.0 开始,作用域继承的变量列表可能包含一个尾部的逗号,这个逗号将被忽略。

这些变量都必须在函数或类的头部声明。从父作用域中继承变量与使用全局变量是不同的。全局变量存在于一个全局的范围,无论当前在执行的是哪个函数。而 闭包的父作用域是定义该闭包的函数(不一定是调用它的函数)。

当在类的上下文中声明时,当前的类会自动与之绑定,使得 $this 在函数的作用域中可用。如果不需要当前类的自动绑定,可以使用 静态匿名函数 替代。

例:

<?php
class User {
    public function test() {
        return function() {
            return $this;
        };
    }
}
$user = new User();
$test = $user->test();
var_dump($test);

运行结果:

静态匿名函数

static关键字,它除了能声明静态变量以外,也可以声明静态匿名函数。匿名函数允许被定义为静态化。这样可以防止当前类自动绑定到它们身上,对象在运行时也可能不会被绑定到它们上面。

例:

<?php
class User {
    public function __construct() {
        $func =  static function() {
            var_dump($this);
        };
        $func();
    }
}
$user = new User();

运行结果:

从上可以看出,在静态匿名函数中去调用$this时,PHP报出了致命错误,不再允许我们这样做。

箭头函数

箭头函数是 PHP 7.4 的新语法,是一种更简洁的 匿名函数 写法。匿名函数和箭头函数都是 Closure 类的实现。箭头函数的基本语法为 fn (argument_list) => expr。

箭头函数支持与 匿名函数 相同的功能,只是其父作用域的变量总是自动的。当表达式中使用的变量是在父作用域中定义的,它将被隐式地按值捕获。在下面的例子中,函数 add1add1和 add2的行为是一样的。

例:

<?php
$y = 1;
$add1 = fn($x) => $x + $y;
echo $add1(1);

echo "\n";

// 相当于通过 value 使用 $y:
$add2 = function ($x) use ($y) {
    return $x + $y;
};

var_export($add2(3));

运行结果:

从上我们可以看出,在使用add1时,我们并没有传入add1时,我们并没有传入y,但PHP还是调用了$y。这是因为箭头函数会自动捕捉变量的值。这样,我们就可以在箭头函数中放心大胆地去使用我们在箭头函数外部所定义的变量。

这种机制在箭头函数嵌套的情况下同样有效。

例:

<?php
$z = 1;
$add = fn($x) => fn($y) => $x + $y + $z;
echo $add(1)(2);

运行结果:

和匿名函数一样,箭头函数语法同样允许标准的函数声明,包括参数和返回类型、缺省值、变量,以及通过引用传递和返回。以下都是箭头函数的有效例子。

 <?php 
    fn(array $x) => $x;
    static fn(): int => $x
    fn($x = 42) => $x;
    fn(&$x) => $x;
    fn&($x) => $x;
    fn($x, ...$rest) => $rest;

箭头函数会自动绑定上下文变量,这相当于对箭头函数内部使用的每一个变量 x执行了一个use(x 执行了一个 use(x)。这意味着不可能修改外部作用域的任何值,若要实现对值的修改,可以使用 匿名函数 来替代。

例:

<?php
$x = 1;
$res = fn() => $x= $x + 1;
var_dump($res());
var_dump($x);

运行结果:

联合类型

通过前面的学习我们知道了通过限定函数的入参类型,可以让我们的代码风格趋于严格。但有时可能希望一个函数能接收多种不同类型的入参,PHP的函数无法像Java那样通过函数重载来实现这样的效果。不过也确实提供了比Java的函数重载更灵活的方式,那就是联合类型,这是PHP8的新特性。

例:

<?php
function addInt(int $a ,int $b) {
    return $a + $b;
}
echo "addInt:".addInt(1,3)."\n";
function addFloat(float $a ,float $b) {
    return $a + $b;
}
echo "addFloat:".addFloat(1.333,3.222)."\n";
//通过联合类型的方式,我们只需要声明一个函数即可
function add(int | float $a , int | float $b) {
    return $a + $b;
}
echo "add:".add(1,2)."\n";
echo "add:".add(1.3333,2.2222)."\n";

运行结果: