做过一段时间的Web开发,我们都知道或者了解JavaScript中有个非常强大的语法,那就是闭包。其实,在PHP中也早就有了闭包函数的功能。早在5.3版本的PHP中,闭包函数就已经出现了。到了7以及后来的现代框架中,闭包函数的使用更是无处不在。在这里,我们就先从基础来了解PHP中闭包的使用吧!

闭包函数(closures)在PHP中都会转换为 Closure 类的实例。在定义时如果是赋值给变量,在结尾的花括号需要添加;分号。闭包函数从父作用域中继承变量,任何此类变量都应该用 use 语言结构传递进去。 PHP 7.1 起,不能传入此类变量:superglobals、 $this 或者和参数重名。

基础语法

闭包的使用非常简单,和JavaScript也非常相似。因为他们都有另外一个别名,叫做匿名函数。


$a = function () {
echo "this is testA";
};
$a(); // this is testA function testA ($a) {
var_dump($a);
}
testA($a); // class Closure#1 (0) {} $b = function ($name) {
echo 'this is ' . $name;
}; $b('Bob'); // this is Bob

我们将$a和$b两个变量直接赋值为两个函数。这样我们就可以使用变量()的形式调用这两个函数了。通过testA()方法,我们可以看出闭包函数是可以当做普通参数传递的,因为它自动转换成为了 Closure 类的实例。


$age = 16;
$c = function ($name) {
echo 'this is ' . $name . ', Age is ' . $age;
}; $c('Charles'); // this is Charles, Age is $c = function ($name) use ($age) {
echo 'this is ' . $name . ', Age is ' . $age;
}; $c('Charles'); // this is Charles, Age is 16

如果我们需要调用外部的变量,需要使用use关键字来引用外部的变量。这一点和普通函数不一样,因为闭包有着严格的作用域问题。对于全局变量来说,我们可以使用use,也可以使用global。但是对于局部变量(函数中的变量)时,只能使用use。这一点我们后面再说。

作用域


function testD(){
global $testOutVar;
echo $testOutVar;
}
$d = function () use ($testOutVar) {
echo $testOutVar;
};
$dd = function () {
global $testOutVar;
echo $testOutVar;
};
$testOutVar = 'this is d';
$d(); // NULL
testD(); // this is d
$dd(); // this is d $testOutVar = 'this is e';
$e = function () use ($testOutVar) {
echo $testOutVar;
};
$e(); // this is e $testOutVar = 'this is ee';
$e(); // this is e $testOutVar = 'this is f';
$f = function () use (&$testOutVar) {
echo $testOutVar;
};
$f(); // this is f $testOutVar = 'this is ff';
$f(); // this is ff

在作用域中,use传递的变量必须是在函数定义前定义好的,从上述例子中可以看出。如果闭包($d)是在变量($testOutVar)之前定义的,那么$d中use传递进来的变量是空的。同样,我们使用global来测试,不管是普通函数(testD())或者是闭包函数($dd),都是可以正常使用$testOutVar的。

在$e函数中的变量,在函数定义之后进行修改也不会对$e闭包内的变量产生影响。这时候,必须要使用引用传递($f)进行修改才可以让闭包里面的变量产生变化。这里和普通函数的引用传递与值传递的概念是相同的。

除了变量的use问题,其他方面闭包函数和普通函数基本没什么区别,比如进行类的实例化:


class G
{}
$g = function () {
global $age;
echo $age; // 16
$gClass = new G();
var_dump($gClass); // G info
};
$g();

类中作用域

关于全局作用域,闭包函数和普通函数的区别不大,主要的区别体现在use作为桥梁进行变量传递时的状态。在类方法中,有没有什么不一样的地方呢?


$age = 18;
class A
{
private $name = 'A Class';
public function testA()
{
$insName = 'test A function';
$instrinsic = function () {
var_dump($this); // this info
echo $this->name; // A Class
echo $age; // NULL
echo $insName; // null
};
$instrinsic(); $instrinsic1 = function () {
global $age, $insName;
echo $age; // 18
echo $insName; // NULL
};
$instrinsic1(); global $age;
$instrinsic2 = function () use ($age, $insName) {
echo $age; // 18
echo $insName; // test A function
};
$instrinsic2(); }
} $aClass = new A();
$aClass->testA();
  • A::testA()方法中的$insName变量,我们只能通过use来拿到。
  • 闭包函数中的$this是调用它的环境的上下文,在这里就是A类本身。闭包的父作用域是定义该闭包的函数(不一定是调用它的函数)。静态闭包函数无法获得$this。
  • 全局变量依然可以使用global获得。

小技巧

了解了闭包的这些特性后,我们可以来看几个小技巧:


$arr1 = [
['name' => 'Asia'],
['name' => 'Europe'],
['name' => 'America'],
]; $arr1Params = ' is good!';
// foreach($arr1 as $k=>$a){
// $arr1[$k] = $a . $arr1Params;
// }
// print_r($arr1); array_walk($arr1, function (&$v) use ($arr1Params) {
$v .= ' is good!';
});
print_r($arr1);

干掉foreach:很多数组类函数,比如array_map、array_walk等,都需要使用闭包函数来处理。上例中我们就是使用array_walk来对数组中的内容进行处理。是不是很有函数式编程的感觉,而且非常清晰明了。


function testH()
{
return function ($name) {
echo "this is " . $name;
};
}
testH()("testH's closure!"); // this is testH's closure!

看到这样的代码也不要懵圈了。PHP7支持立即执行语法,也就是JavaScript中的IIFE(Immediately-invoked function expression)。

我们再来一个计算斐波那契数列的:


$fib = function ($n) use (&$fib) {
if ($n == 0 || $n == 1) {
return 1;
} return $fib($n - 1) + $fib($n - 2);
}; echo $fib(10);

同样的还是使用递归来实现。这里直接换成了闭包递归来实现。最后有一点要注意的是,use中传递的变量名不能是带下标的数组项:


$fruits = ['apples', 'oranges'];
$example = function () use ($fruits[0]) { // Parse error: syntax error, unexpected '[', expecting ',' or ')'
echo $fruits[0];
};
$example();

这样写直接就是语法错误,无法成功运行的。

彩蛋

Laravel中的IoC服务容器中,大量使用了闭包能力,我们模拟一个便于大家理解。当然,更好的方案是自己去翻翻Laravel的源码。

class B
{}
class C
{}
class D
{}
class Ioc
{
public $objs = [];
public $containers = []; public function __construct()
{
$this->objs['b'] = function () {
return new B();
};
$this->objs['c'] = function () {
return new C();
};
$this->objs['d'] = function () {
return new D();
};
}
public function bind($name)
{
if (!isset($this->containers[$name])) {
if (isset($this->objs[$name])) {
$this->containers[$name] = $this->objs[$name]();
} else {
return null;
}
}
return $this->containers[$name];
}
} $ioc = new Ioc();
$bClass = $ioc->bind('b');
$cClass = $ioc->bind('c');
$dClass = $ioc->bind('d');
$eClass = $ioc->bind('e'); var_dump($bClass); // B
var_dump($cClass); // C
var_dump($dClass); // D
var_dump($eClass); // NULL

总结

闭包特性经常出现的地方是事件回调类的功能中,另外就是像彩蛋中的IoC的实现。因为闭包有一个很强大的能力就是可以延迟加载。IoC的例子我们的闭包中返回的是新new出来的对象。当我们的程序运行的时候,如果没有调用$ioc->bind('b'),那么这个B对象是不会创建的,也就是说这时它还不会占用资源占用内存。而当我们需要的时候,从服务容器中拿出来的时候才利用闭包真正的去创建对象。同理,事件的回调也是一样的概念。事件发生时在我们需要处理的时候才去执行回调里面的代码。如果没有闭包的概念,那么$objs容器就这么写了:


$this->objs['b'] = new B();
$this->objs['c'] = new C();
$this->objs['d'] = new D();

容器在实例化的时候就把所有的类都必须实例化了。这样对于程序来说很多用不上的对象就都被创建了,带来非常大的资源浪费。

基于闭包的这种强大能力,现在闭包函数已经在Laravel、TP6等框架中无处不在了。学习无止尽,掌握原理再去学习框架往往更能事半功倍。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/201911/source/%E8%BF%98%E4%B8%8D%E7%9F%A5%E9%81%93PHP%E6%9C%89%E9%97%AD%E5%8C%85%EF%BC%9F%E9%82%A3%E4%BD%A0%E7%9C%9FOUT%E4%BA%86.php

参考文档:

https://www.php.net/manual/zh/functions.anonymous.php

https://www.php.net/manual/zh/functions.anonymous.php#100545

https://www.php.net/manual/zh/functions.anonymous.php#119388

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

还不知道PHP有闭包?那你真OUT了的更多相关文章

  1. 你还不知道Vue的生命周期吗?带你从Vue源码了解Vue2.x的生命周期(初始化阶段)

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...

  2. JDK15就要来了,你却还不知道JDK8的新特性!

    微信搜「烟雨星空」,白嫖更多好文. 现在 Oracle 官方每隔半年就会出一个 JDK 新版本.按时间来算的话,这个月就要出 JDK15 了.然而,大部分公司还是在使用 JDK7 和 8 . 之前去我 ...

  3. 什么?作为程序员的你还不知道怎么访问 Google

    今天就一个目的,让你可以FQ成功,其他人我不知道,但就程序员来说,不能使用 Google 那真是一大损失,当然还有对所有人适用的 YouTobu 这个视频网站,资源多的没话说,别的不说,学习英语很方便 ...

  4. 使用过Redis,我竟然还不知道Rdb

    目录 使用过Redis,那就先说说使用过那些场景吧 Rdb文件是什么,它是干什么的 分析工具 小结 联想 推荐阅读 使用过Redis,那就先说说使用过那些场景吧 字符串缓存 //举例 $redis-& ...

  5. 听说你还不理解JavaScript闭包

    闭包(Closure) 闭包是一个函数和词法环境的组合,函数声明在这个词法环境中 词法作用域 看下面一个例子 function init() { var name = 'Mozilla'; // na ...

  6. 阿里面试官必问的12个MySQL数据库基础知识,哪些你还不知道?

    数据库基础知识 1.为什么要使用数据库 (1)数据保存在内存 优点: 存取速度快 缺点: 数据不能永久保存 (2)数据保存在文件 优点: 数据永久保存 缺点: 1)速度比内存操作慢,频繁的IO操作. ...

  7. .NET5都来了,你还不知道怎么部署到linux?最全部署方案,总有一款适合你

    随着2020进入4季度,.NET5正式版也已经与大家见面了.不过,尽管 .NET Core发布已经有四五年的时间,但到目前为止,依旧有很多.NET开发者在坚守者.NET4,原因不尽相同,但最大的问题可 ...

  8. 听说你还不知道Spring是如何解决循环依赖问题的?

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

  9. 听说你还不知道Java代码是怎么运行的?

    作为一名Java程序员,我们需要知道Java代码是怎么运行的.最近复习了深入理解Java虚拟机这本书,做了一下笔记,希望对大家有帮助,如果有不正确的地方,欢迎提出,感激不尽. java 代码运行主要流 ...

随机推荐

  1. Shell-09-文本处理awk

    awk 详情见: awk

  2. spring-security oauth2.0简单集成

    github地址:https://github.com/intfish123/oauth.git 需要2个服务,一个认证授权服务,一个资源服务 认证授权服务为客户端颁发令牌,资源服务用于客户端获取用户 ...

  3. 一次关于shiro反序列化漏洞的思考

    0x01前言 之前在我反序列化的那篇文章中(https://www.cnblogs.com/lcxblogs/p/13539535.html),简单说了一下反序列化漏洞,也提了一嘴常见的几种Java框 ...

  4. 题解 P3317 [SDOI2014]重建

    题解 前置芝士:深度理解的矩阵树定理 矩阵树定理能求生成树个数的原因是,它本质上求的是: \[\sum_T \prod_{e\in T} w_e \] 其中 \(w_e\) 是边权,那么我们会发现其实 ...

  5. spring cloud 的hystrix 熔断器 和feign 调用的使用

    1, 添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId&g ...

  6. .NET WebApi 实战第五讲之EntityFramework事务

    在<.NET WebApi 实战第二讲>中我们有提到过事务的概念!任何数据库的读操作可以没有事务,但是写事件必须有事务,如果一个后端工程师在数据库写入时未添加事务,那就不是一个合格的工程师 ...

  7. 单例模式-案例Runtime

    package d.create_type_single; import java.io.IOException; /** * Runtime类就是使用的单例:并且是饿汉式 * (原因考虑是因为:多线 ...

  8. java 常用Bean 转换工具类

    package com.hnf.framework.utils; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.data ...

  9. mac下用clion进行sdl2游戏开发de环境搭建

    1. 故事背景 想从unity转unreal了,于是要使用c++进行开发.unreal引擎那么大,每次打开,我的小本都嗡嗡嗡的,想着不如用个轻量一些的引擎先开发吧,核心代码独立出来,到时候如果真要移植 ...

  10. Qt5中用QLCDNumber显示时间

    编程中经常要用到时间的显示,因此在这总结一下在Qt中如何显示时间.废话不多说,直接上代码,简单明了,一看就懂~~ mydialog.h 文件 #ifndef MYDIALOG_H #define MY ...