闭包的介绍
闭包是词法作用域的体现,一般编程语言有这些特性
- 函数是一阶值(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
- 函数可以嵌套定义,即在一个函数内部可以定义另一个函数。
三个关键点
- 函数
- 自由变量
- 作用域
自由变量
let a = 1
let b = function(){
console.log(a)
}
在这个例子里函数b因为捕获了外部作用域(环境)中的变量a,因此形成了闭包。 而由于变量a并不属于函数b,所以在概念里被称之为「自由变量」。
注意点:
- 在一些语言里,「捕获」这一行为还有一个特点:闭包只会捕获自由变量的引用。
- 在PHP中,需要使用
use
关键词显示调用自由变量
作用域
可以使用外部的「自由变量」,内部变量不能被访问,形成一个相对安全的访问环境
总结
- 记住定义当时的外部环境
- 封闭外部环境
- 延迟执行
基本使用
$func = function () {
};
$func();
$a = '1';
$func = function () use ($a) {
}
$func();
作用域
- 闭包拥有独立的作用域,基本等同普通函数
- 在方法中使用时,等同于实例的方法,可以直接使用$this(>=5.4)
注意点
$this 的使用
在一次开发中遇到关于闭包的一个问题
class Demo
{
/**
*
*/
public function test()
{
$data = $this->isCache('name', function () {
$this->sayHello();
});
}
/**
*
*/
protected function sayHello()
{
echo 'Hello, World';
}
/**
* @param $name
* @param $callback
* @return array
*/
public function isCache($name, $callback)
{
$callback();
return array();
}
}
我希望在一个闭包中调用这个类的一个受保护的方法(这里假设是sayHello),结果在运行的时候,直接出现了语法级别的报错。
查了一些资料发现了问题
- 5.3 版本不能直接在闭包中使用$this
PHP的bug,在5.4中得以修复
$that = $this;
$data = $this->isCache('name', function () use ($that) {
$that->sayHello(); // 这里只能是public
});
看似这个问题解决了,不过在stackoverflow上一位热心人士的提醒中,这样是会出现一些问题的,因为这样传递$this, 是赋值传递,也就是说,在闭包中调用方法导致实例属性的变化不会反应到闭包外面,这样在一些特殊情况,会导致闭包内外属性不一致,所以这里最好使用引用传递,除非你知道你在干什么!
$that = $this;
$data = $this->isCache('name', function () use (&$that) {
$that->sayHello(); // 这里只能是public
});
如果在5.4版本下,不但可以直接使用$this,而且作用域等同于实例中的方法
$data = $this->isCache('name', function () {
$that->sayHello(); // 这里可以是任意限制符
});
Closure类
- Closure::__construct — Constructor that disallows instantiation
- Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
- Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
- Closure::call — 绑定指定的$this对象和类作用域并执行
- Closure::fromCallable — 将一个可执行函数转换为闭包函数
bind和bindTo的区别
// bind,相当把一个类的加上加上一个方法,并且将该方法转换成闭包
class A {
private static $sfoo = 1;
private $ifoo = 2;
}
$cl1 = static function() {
return A::$sfoo;
};
$cl2 = function() {
return $this->ifoo;
};
$bcl1 = Closure::bind($cl1, null, 'A');
$bcl2 = Closure::bind($cl2, new A(), 'A');
echo $bcl1(), "\n"; // 1
echo $bcl2(), "\n";// 2
// bindTo,改变指定的$this对象和类作用域
class A {
function __construct($val) {
$this->val = $val;
}
function getClosure() {
//returns closure bound to this object and scope
return function() { return $this->val; };
}
}
$ob1 = new A(1);
$ob2 = new A(2);
$cl = $ob1->getClosure();
echo $cl(), "\n";
$cl = $cl->bindTo($ob2);
echo $cl(), "\n";
虽然两者看上去像,实际上不是一回事。