前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP 函数篇(下):匿名函数和作用域

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

作者头像
学院君
发布2020-06-02 14:30:58
8950
发布2020-06-02 14:30:58
举报
文章被收录于专栏:学院君的专栏学院君的专栏

1、匿名函数

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

编写匿名函数

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

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

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

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

代码语言:javascript
复制
1 + 2 = 3

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

代码语言:javascript
复制
$sum = call_user_func($add, $a, $b);

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

可变数量的参数列表

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

代码语言:javascript
复制
function call_user_func ($function, ...$parameter)

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

默认参数

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

代码语言:javascript
复制
$add = function (int $a, int $b = 2): int {
    return $a + $b;
};

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

代码语言:javascript
复制
$n1 = 1;
$n2 = 2;
$sum = $add($n1);
echo "$n1 + $n2 = $sum" . PHP_EOL;

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

代码语言:javascript
复制
$n1 = 1;
$n2 = 3;
$sum = $add($n1, $n2);
echo "$n1 + $n2 = $sum" . PHP_EOL;

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

代码语言:javascript
复制
1 + 3 = 4
可变函数

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

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

代码语言:javascript
复制
<?php

/**
 * 通过匿名函数定义两数相加函数 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;

打印结果都是一样的:

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

2、作用域

继承父作用域变量

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

代码语言:javascript
复制
<?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 关键字通过全局变量引用函数体外部定义的变量:

代码语言:javascript
复制
// 计算两数相减
function sub() {
    global $n1, $n2;
    return $n1 - $n2;
}
global vs. 匿名函数

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

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

代码语言:javascript
复制
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,全局变量存在于全局范围,与调用位置无关,所以可以成功引用。

上述代码的执行结果是:

global 的安全隐患

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

代码语言:javascript
复制
function test() {
    printf("n1 = %d, n2 = %d, n3 = %d\n", $GLOBALS['n1'], $GLOBALS['n2'], $GLOBALS['n3']);
}

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极客书房 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、匿名函数
    • 编写匿名函数
      • 可变数量的参数列表
        • 默认参数
          • 可变函数
          • 2、作用域
            • 继承父作用域变量
              • 通过 global 声明全局变量
                • global vs. 匿名函数
                  • global 的安全隐患
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档