Php中方法作用域,PHP 函数(下):匿名函数和作用域

PHP 函数(下):匿名函数和作用域

由 学院君 创建于9个月前, 最后更新于 7个月前

版本号 #2

2282 views

1 likes

1 collects

匿名函数

从 PHP 5.3 开始,引入了对匿名函数的支持,所谓匿名函数就是在函数定义中没有显式声明函数名,在 PHP 中,匿名函数也被称作闭包函数(Closure)。

编写匿名函数

我们在 php_learning/function 目录下创建 closure.php 来存放本篇教程编写的代码。 以上篇教程演示的自定义函数 add 为例,如果通过匿名函数进行定义,就是这样的:

image-15891892843129.jpg

上面第一个红色方框里面是匿名函数的定义部分,可以看到在 function 之后没有声明函数名,而是将整个函数赋值给了 $add 变量(不要漏掉赋值语句最后的分号),这样,$add 就变成了函数类型,也因此,函数在 PHP 中也可以看作是一等公民(first class),可以赋值给变量进行调用,此时,如果我们试图通过 var_dump($add) 打印 $add,结果如下:

d6af3946b4740516bcdddc84fe6d8e98.png

可以看到它的类型是用于代表匿名函数的 Closure 类,并且该匿名函数支持两个必填参数 $a 和 $b。

回到 closure.php,在上述截图的第二个红色方框区域是匿名函数的调用部分,我们可以直接将 $add 作为一个函数名进行调用,打印结果是:

1 + 2 = 3

此外,还可以通过 PHP 内置的 call_user_func 函数调用该函数,第一个参数是函数名,后面的参数是函数参数(非匿名函数亦可通过 call_user_func 函数调用):

$sum = call_user_func($add, $a, $b);

返回结果和上面的 $add($a, $b) 完全一致。

可变数量的参数列表

如果感兴趣的话,看 call_user_func 函数的声明:

function call_user_func ($function, ...$parameter)

可以看到代表参数列表的 $parameter 前面有一个 ... 前缀,其作用是标识该参数是一个可变数量的参数列表,也就是支持传入任意多个参数,从 0~N 个不等,比如我们这里传入的就是 $a 和 $b 两个参数,如果待调用函数 $function 不需要传递参数,则 $parameter 部分留空,如果只需要传入一个参数,则传入一个参数,依此类推。

默认参数

说到这里,我们还可以为函数设置默认参数,即为指定参数设置默认值,需要注意的是默认参数需要放到参数列表最后:

$add = function (int $a, int $b = 2): int {

return $a + $b;

};

这个时候,调用 $add 函数就可以不传入第二个参数了,该参数会使用默认参数值:

$n1 = 1;

$n2 = 2;

$sum = $add($n1);

echo "$n1 + $n2 = $sum" . PHP_EOL;

当然,你可以可以传入第二个参数覆盖默认值:

$n1 = 1;

$n2 = 3;

$sum = $add($n1, $n2);

echo "$n1 + $n2 = $sum" . PHP_EOL;

这样打印的结果就变成了:

1 + 3 = 4

可变函数

最后,由于 $add 是一个函数类型变量,并且 PHP 是动态类型语言,所以我们还可以像操作基本类型变量那样将其他函数类型值赋值给 $add,这些函数类型值包括匿名函数和非匿名函数,比如我们新增一个两数相乘函数 multi,然后在运行时将其赋值给 $add:

421e7ca9c6c05553ecdb5815f1821f1c.png

注意第二个红色方框,我们在运行时将 multi 函数赋值给 $add,再调用 $add($n1, $n2) 则等同于调用 multi($n1, $n2),当然如果通过匿名函数定义 multi 也是可以的,对应的实现代码如下:

/**

* 通过匿名函数定义两数相加函数 add

* @param int $a

* @param int $b

* @return int

*/

$add = function (int $a, int $b = 2): int {

return $a + $b;

};

/**

* 两数相乘函数 multi

* @param int $a

* @param int $b

* @return int

*/

$multi = function (int $a, int $b): int {

return $a * $b;

};

// 调用匿名函数

$n1 = 1;

$n2 = 3;

$sum = $add($n1, $n2);

echo "$n1 + $n2 = $sum" . PHP_EOL;

// 将 multi 赋值给 $add

$add = $multi;

$product = $add($n1, $n2);

echo "$n1 x $n2 = $product" . PHP_EOL;

打印结果都是一样的:

422690428d15a814ff25ce5612ba5e66.png

这种在运行时动态设置函数类型值给变量的功能,在 PHP 中称之为可变函数。

作用域

继承父作用域变量

匿名函数(或者叫闭包函数)的一个强大功能是支持在函数体中直接引用上下文变量(继承父作用域的变量),比如在上述代码中,我们可以这样编写匿名函数实现代码:

$n1 = 1;

$n2 = 3;

// 计算两数相加

$add = function () use ($n1, $n2) {

return $n1 + $n2;

};

// 计算两数相乘

$multi = function () use ($n1, $n2){

return $n1 * $n2;

};

// 调用匿名函数

$sum = $add();

echo "$n1 + $n2 = $sum" . PHP_EOL;

$product = $multi();

echo "$n1 x $n2 = $product" . PHP_EOL;

只需要通过 use 关键字传递当前上下文中的变量,它们就可以在闭包函数体中直接使用,而不需要通过参数形式传入,这样一来,其他引用该文件的代码就可以间接引用当前父作用域下的变量,如果是在类方法中定义的匿名函数,则可以直接引用相应类实例的属性,关于这一块,学院君会在后续面向对象编程中详细介绍。

通过 global 声明全局变量

如果不是通过匿名函数的话,只能基于 global 关键字通过全局变量引用函数体外部定义的变量:

// 计算两数相减

function sub() {

global $n1, $n2;

return $n1 - $n2;

}

global vs. 匿名函数

从父作用域中继承变量与使用全局变量是不同的,全局变量存在于一个全局的范围,无论当前在执行的是哪个函数,而闭包的父作用域是定义该闭包的函数,不一定是调用它的函数。

我们编写一段示例代码来详细解释:

function add1($n1, $n2) {

return function () use ($n1, $n2) {

return $n1 + $n2;

};

}

function add2() {

return function () {

global $n1, $n2, $n3;

return $n1 + $n2 + $n3;

};

}

$n1 = 1;

$n2 = 3;

$n3 = 4;

$add = add1($n1, $n2);

$sum = $add();

echo "$n1 + $n2 = $sum" . PHP_EOL;

$add = add2();

$sum = $add();

echo "$n1 + $n2 + $n3 = $sum" . PHP_EOL;

在上述代码中,add1 中定义的闭包函数通过 use 引用了父作用域下的 $n1 和 $n2 变量,对于该闭包函数来说,其作用域是 add1 函数,而非调用它的位置,所以如果我们试图在 add1 中定义的闭包函数中通过 use 引用 $n3 会报错。

而 add2 中定义的闭包函数通过 global 关键字以全局变量的方式引用 $n1、$n2 和 $n3,全局变量存在于全局范围,与调用位置无关,所以可以成功引用。

上述代码的执行结果是:

c3460ae5716749b0e44427705ad3d263.png

global 的安全隐患

但实际编码中,尽量避免使用 global 关键字,因为一旦声明了全局变量,就可以在任何位置获取到这些全局变量,非常容易导致系统被攻击,比如我们新增一个函数 test,在这个函数内部就可以试图通过 $GLOBALS 获取对应全局变量:

function test() {

printf("n1 = %d, n2 = %d, n3 = %d\n", $GLOBALS['n1'], $GLOBALS['n2'], $GLOBALS['n3']);

}

匿名函数则有效规避了这种安全隐患。此外,匿名函数的另一个典型应用场景就是兜底处理(fallback),关于这一点,学院君将在作业项目中演示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值