学习一下闭包函数 - Closures

什么是闭包?

PHP 文档这样介绍:

匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。当然,也有其它应用的情况。

真的很含糊,什么叫还有其它应用的情况。。。

找了 JavaScript 的文档:

Closures (闭包)是使用被作用域封闭的变量,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)。

闭包是一个函数和声明该函数的词法环境的组合。从理论角度来说,所有函数都是闭包。

关键在于“作用域”、“环境”。

栗子:

<?php
$closure = function($name) {
    printf("Hello %s\r\n", $name);
};

$closure('World');
// Hello World

为什么要用闭包?

闭包有一个特点,内部函数可以引用外部函数的参数和变量,参数和变量就不会被收回。

环境被保存下来。

栗子:

<?php
$add = function() {
    $sum = 0;
    return function() use (&$sum): int {
        $sum += 1;
        return $sum;
    };
};

$test = $add();

echo $test(), "\n"; // 1
echo $test(), "\n"; // 2

一般函数局部变量无法长久地保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久地保存变量又不会造成全局污染。

PHP 闭包的实现原理

目前是通过 Closure 类来实现,调用闭包函数的过程与 __invoke 魔术方法无关。

这是文档说的,但是很奇怪,下面的代码两个输出是一样的。

<?php
$closure = function($name) {
    printf("Hello %s\r\n", $name);
};

$closure('World'); // Hello World
$closure->__invoke('World'); // Hello World

原理网上找了下,比较少相关文章。

与 JavaScript 闭包区别

最大的区别在于作用域。

可以看到前面加一的例子,使用了 use 关键字,PHP 里叫做变量“继承”,而且逐层传递(只能传递父级作用域的变量)。

这跟 PHP 语言特性有关, PHP 只有函数作用域、类作用域,而没有块级作用域。

<?php

$i = 1;
while($i--) {
    $j = 0;
}

echo $j, "\n"; // 0

奇葩有木有。。。

函数里访问不了函数外部的变量,需要访问的话可以通过传参和 use 传递。

下面的例子可以更好看出区别。

举栗子

使用闭包打印斐波那契数列。我们知道斐波那契数列有下面的规律:

# f(n) 表示数列中第 n 个数的值
f(n) = 0; (n = 0)
f(n) = 1; (n = 1)
f(n) = f(n-1)+f(n-2); (n >= 2)

使用递归的方法

<?php
function fibonacci(int $n): int {
    if ($n < 2) {
        return $n;
    }
    return fibonacci($n-1) + fibonacci($n-2);
}

echo fibonacci(10), "\n"; // 55

如果打印数列,那每一次都需要重复计算前面已经计算过的数据(只需要前两个就好)。

可以使用闭包保存上一次的运行环境。

PHP 版本

<?php
$fibonacci = function (): callable {
    $x = 0;
    $y = 1;
    return function () use (&$x, &$y): int {
        list($x, $y) = [$y, $x+$y];
        return $x;
    };
};

$f = $fibonacci();

for ($i = 0; $i < 10; $i++) {
    echo $f() , "\n";
}

JavaScript 版本

let fibonacci = _ => {
    let x = 0, y = 1;
    return _ => {
        [x, y] = [y, x+y];
        return x;
    };
};

let f = fibonacci();

for (let i = 0; i <= 10; i++) {
    console.log(f());
}

对,说了那么多我就是想证明 JavaScript ES6 好简洁。

本作品采用《CC 协议》,转载必须注明作者和本文链接
癞蛤蟆想吃炖大鹅
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 8

其实要保证一个函数内变量不被立即销毁也可以把函数内局部变量声明为static,这样函数内的这个变量就会在一次请求生命周期内都存在(除非手动销毁),我个人觉得吧,PHP的闭包更多用在回调,比如事件机制,请求管道处理等实现上。

6年前 评论

function(): int{}这样写是什么意思啊?

6年前 评论

@passenger 函数返回值类型是 int 类型哦

6年前 评论

@Miaoaotian function (): callable {那callable是指什么呀:sweat_smile:?

6年前 评论

@passenger 回调类型 你可以看下手册,手册解释的详细 http://php.net/manual/zh/language.types.ca...

6年前 评论

@passenger callback 在php表示可调用结构类型,具体可看文档

6年前 评论

@Miaoaotian 好的,谢谢啦!

6年前 评论

@JimChen 嗯,谢谢!

6年前 评论

其实要保证一个函数内变量不被立即销毁也可以把函数内局部变量声明为static,这样函数内的这个变量就会在一次请求生命周期内都存在(除非手动销毁),我个人觉得吧,PHP的闭包更多用在回调,比如事件机制,请求管道处理等实现上。

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!