一、介绍

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

二、使用场景

1、动态调用静态类的时候

<?php
class test
{
public static function getinfo()
{
var_dump(func_get_args());
}
} call_user_func(array('test', 'getinfo'), 'hello world');

2、在callback函数中使用

<?php
//eg array_walk array_map preg_replace_callback etc echo preg_replace_callback('~-([a-z])~', function ($match) {
return strtoupper($match[1]);
}, 'hello-world');
// 输出 helloWorld
?>

3、赋值给一个普通的变量

<?php
$greet = function($name)
{
printf("Hello %s\r\n", $name);
}; $greet('World');
$greet('PHP');
?>

4、使用use从父域中继承

<?php
$message = 'hello'; // 继承 $message
$example = function () use ($message) {
var_dump($message);
};
echo $example(); // Inherit by-reference
$example = function () use (&$message) {
var_dump($message);
};
echo $example(); // The changed value in the parent scope
// is reflected inside the function call
$message = 'world';
echo $example();

5、传递参数

<?php
$example = function ($arg) use ($message) {
var_dump($arg . ' ' . $message);
};
$example("hello");

6、OO中的使用

<?php

class factory{
private $_factory;
public function set($id,$value){
$this->_factory[$id] = $value;
} public function get($id){
$value = $this->_factory[$id];
return $value();
}
}
class User{
private $_username;
function __construct($username="") {
$this->_username = $username;
}
function getUserName(){
return $this->_username;
}
} $factory = new factory(); $factory->set("zhangsan",function(){
return new User('张三');
});
$factory->set("lisi",function(){
return new User("李四");
});
echo $factory->get("zhangsan")->getUserName();
echo $factory->get("lisi")->getUserName();

7、函数中的调用

<?php

function call($callback){
$callback();
}
call(function() {
var_dump('hell world');
});

三、分析

第一个例子

[root@chenpingzhao www]# php-cgi -dvld.active=1 k1.php
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = -2
filename: /data/www/k1.php
function name: (null)
number of ops: 11
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
4 0 E > EXT_STMT
11 1 EXT_STMT
2 EXT_FCALL_BEGIN
3 INIT_ARRAY ~0 'foo'
4 ADD_ARRAY_ELEMENT ~0 'func'
5 SEND_VAL ~0
6 INIT_ARRAY ~0 'hello+world'
7 SEND_VAL ~0
8 DO_FCALL 2 'call_user_func_array'
9 EXT_FCALL_END
12 10 > RETURN 1 branch: # 0; line: 4- 12; sop: 0; eop: 10; out1: -2
path #1: 0,
Class foo:
Function func:
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = -2
filename: /data/www/k1.php
function name: func
number of ops: 11
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
5 0 E > EXT_NOP
7 1 EXT_STMT
2 EXT_FCALL_BEGIN
3 EXT_FCALL_BEGIN
4 DO_FCALL 0 $0 'func_get_args'
5 EXT_FCALL_END
6 SEND_VAR_NO_REF 6 $0
7 DO_FCALL 1 'var_dump'
8 EXT_FCALL_END
8 9 EXT_STMT
10 > RETURN null branch: # 0; line: 5- 8; sop: 0; eop: 10; out1: -2
path #1: 0,
End of function func End of class foo. X-Powered-By: PHP/5.5.23
Content-type: text/html

没有这个DECLARE_LAMBDA_FUNCTION 这个步骤,说明这个和自己实现的闭包是两码事

第三个例子比较简单,我们分析一下好了

[root@localhost www]# php-cgi -dvld.active=1 k3.php
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = -2
filename: /data/www/k3.php
function name: (null)
number of ops: 17
compiled vars: !0 = $greet
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > EXT_STMT
1 DECLARE_LAMBDA_FUNCTION '%00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff017'
5 2 ASSIGN !0, ~0
7 3 EXT_STMT
4 INIT_FCALL_BY_NAME !0
5 EXT_FCALL_BEGIN
6 SEND_VAL 'World'
7 DO_FCALL_BY_NAME 1
8 EXT_FCALL_END
8 9 EXT_STMT
10 INIT_FCALL_BY_NAME !0
11 EXT_FCALL_BEGIN
12 SEND_VAL 'PHP'
13 DO_FCALL_BY_NAME 1
14 EXT_FCALL_END
10 15 EXT_STMT
16 > RETURN 1 branch: # 0; line: 2- 10; sop: 0; eop: 16; out1: -2
path #1: 0,
Function %00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff01:
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = -2
filename: /data/www/k3.php
function name: {closure}
number of ops: 10
compiled vars: !0 = $name
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > EXT_NOP
1 RECV !0
4 2 EXT_STMT
3 EXT_FCALL_BEGIN
4 SEND_VAL 'Hello+%25s%0D%0A'
5 SEND_VAR !0
6 DO_FCALL 2 'printf'
7 EXT_FCALL_END
5 8 EXT_STMT
9 > RETURN null branch: # 0; line: 2- 5; sop: 0; eop: 9; out1: -2
path #1: 0,
End of function %00%7Bclosure%7D%2Fdata%2Fwww%2Fk3.php0xa67ff01 X-Powered-By: PHP/5.5.23
Content-type: text/html Hello World
Hello PHP

让我看一下底层是怎么实现的:Zend/zend_vm_execute.h

其实用的应该是LAMBDA_FUNCTION

static int ZEND_FASTCALL  ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_function *op_array;
int closure_is_static, closure_is_being_defined_inside_static_context; SAVE_OPLINE(); if (UNEXPECTED(zend_hash_quick_find(EG(function_table), Z_STRVAL_P(opline->op1.zv), \
Z_STRLEN_P(opline->op1.zv), Z_HASH_P(opline->op1.zv), (void *) &op_array) == FAILURE) ||
UNEXPECTED(op_array->type != ZEND_USER_FUNCTION)) {
zend_error_noreturn(E_ERROR, "Base lambda function for closure not found");
} closure_is_static = op_array->common.fn_flags & ZEND_ACC_STATIC;
closure_is_being_defined_inside_static_context = EX(prev_execute_data) &&\
EX(prev_execute_data)->function_state.function->common.fn_flags & ZEND_ACC_STATIC;
if (closure_is_static || closure_is_being_defined_inside_static_context) {
//关键函数在这里
zend_create_closure(&EX_T(opline->result.var).tmp_var, (zend_function *) op_array, EG(called_scope), NULL TSRMLS_CC);
} else {
zend_create_closure(&EX_T(opline->result.var).tmp_var, (zend_function *) op_array, EG(scope), EG(This) TSRMLS_CC);
} CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

我们再看一下zend_create_closure具体是怎么实现的:Zend/zend_closures.c

ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zval \
*this_ptr TSRMLS_DC) /* {{{ */
{
zend_closure *closure; object_init_ex(res, zend_ce_closure);//初始化 closure = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC); closure->func = *func;
closure->func.common.prototype = NULL;
closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; if ((scope == NULL) && (this_ptr != NULL)) {
/* use dummy scope if we're binding an object without specifying a scope */
/* maybe it would be better to create one for this purpose */
scope = zend_ce_closure;
} if (closure->func.type == ZEND_USER_FUNCTION) {//用户自定函数
if (closure->func.op_array.static_variables) {
HashTable *static_variables = closure->func.op_array.static_variables;
//hash表,申请内存、初始化
ALLOC_HASHTABLE(closure->func.op_array.static_variables);
zend_hash_init(closure->func.op_array.static_variables, \
zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0);
//对变量赋值 zval_copy_static_var 这儿是静态变量
zend_hash_apply_with_arguments(static_variables TSRMLS_CC,\
(apply_func_args_t)zval_copy_static_var, 1, closure->func.op_array.static_variables);
}
closure->func.op_array.run_time_cache = NULL;
(*closure->func.op_array.refcount)++;
} else {
//绑定错误
/* verify that we aren't binding internal function to a wrong scope */
if(func->common.scope != NULL) {
if(scope && !instanceof_function(scope, func->common.scope TSRMLS_CC)) {
zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s",\
func->common.scope->name, func->common.function_name, scope->name);
scope = NULL;
}
if(scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 &&
!instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope TSRMLS_CC)) {
zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s",\
func->common.scope->name, func->common.function_name, Z_OBJCE_P(this_ptr)->name);
scope = NULL;
this_ptr = NULL;
}
} else {
/* if it's a free function, we won't set scope & this since they're meaningless */
this_ptr = NULL;
scope = NULL;
}
} closure->this_ptr = NULL;
/* Invariants:
* If the closure is unscoped, it has no bound object.
* The the closure is scoped, it's either static or it's bound */
closure->func.common.scope = scope;
if (scope) {
closure->func.common.fn_flags |= ZEND_ACC_PUBLIC;
if (this_ptr && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) {
closure->this_ptr = this_ptr;
Z_ADDREF_P(this_ptr);
} else {
closure->func.common.fn_flags |= ZEND_ACC_STATIC;
}
}
}
/* }}} */

下面我看看变量是如何赋值的:zend/zend_variables.c

ZEND_API int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args, \
zend_hash_key *key) /* {{{ */
{
HashTable *target = va_arg(args, HashTable*);//定一个一个hashtable
zend_bool is_ref;//是否为引用变量
zval *tmp; if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {//变量作用域 use的时候
is_ref = Z_TYPE_PP(p) & IS_LEXICAL_REF; if (!EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C);
}
if (zend_hash_quick_find(EG(active_symbol_table), key->arKey, key->nKeyLength, \
key->h, (void **) &p) == FAILURE) {
if (is_ref) {
ALLOC_INIT_ZVAL(tmp);
Z_SET_ISREF_P(tmp);
zend_hash_quick_add(EG(active_symbol_table), key->arKey, key->nKeyLength, \
key->h, &tmp, sizeof(zval*), (void**)&p);
} else {
tmp = EG(uninitialized_zval_ptr);
zend_error(E_NOTICE,"Undefined variable: %s", key->arKey);
}
} else {
if (is_ref) {
SEPARATE_ZVAL_TO_MAKE_IS_REF(p);
tmp = *p;
} else if (Z_ISREF_PP(p)) {
ALLOC_INIT_ZVAL(tmp);
ZVAL_COPY_VALUE(tmp, *p);
zval_copy_ctor(tmp);
Z_SET_REFCOUNT_P(tmp, 0);
Z_UNSET_ISREF_P(tmp);
} else {
tmp = *p;
}
}
} else {
tmp = *p;
}
if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, &tmp, \
sizeof(zval*), NULL) == SUCCESS) {
Z_ADDREF_P(tmp);
}
return ZEND_HASH_APPLY_KEEP;
}
/* }}} */

参考:http://php.net/manual/zh/function.call-user-func-array.php

深入了解PHP闭包的使用以及实现的更多相关文章

  1. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  2. 干货分享:让你分分钟学会 JS 闭包

    闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概念进行大篇幅描述 ...

  3. 深入浅出JavaScript之闭包(Closure)

    闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面写下我的学习笔记~ 闭包-无处不 ...

  4. javascript之闭包理解以及应用场景

    半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...

  5. js闭包 和 prototype

    function test(){ var p=200; function q(){ return p++; } return q; } var s = test(); alert(s()); aler ...

  6. js闭包for循环总是只执行最后一个值得解决方法

    <style> li{ list-style: none;width:40px;height: 40px;text-align:center;line-height: 40px;curso ...

  7. JavaScript学习笔记(二)——闭包、IIFE、apply、函数与对象

    一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...

  8. 带你一分钟理解闭包--js面向对象编程

    上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ...

  9. 如何设计一门语言(七)——闭包、lambda和interface

    人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是 ...

  10. JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

随机推荐

  1. 修改后无警告全面支持non-ARC以及ARC的OpenUDID

    OpenUDID Open source initiative for a universal and persistent UDID solution for iOS. 首创的给iOS提供设备唯一标 ...

  2. iptables配置实践

    前言 在大企业中防火墙角色主要交给硬件来支持,效果自然没话说只是需要增加一点点成本,但对于大多数个人或者互联网公司来说选择系统自带的iptables或者第三方云防火墙似乎是更加合适的选择,通过一些合理 ...

  3. Java:IO流的综合用法(从键盘录入数据并打印在控制台上)

    import java.io.*; public class IOTestDouble { public static void main(String[] args)throws Exception ...

  4. mybatis异常 :元素内容必须由格式正确的字符数据或标记组成。

    今天同事写一个查询接口的时候,出错:元素内容必须由格式正确的字符数据或标记组成. 错误原因:mybatis查询的时候,需要用到运算符 小于号:< 和  大于号: >,在mybatis配置文 ...

  5. JQuery缓冲加载图片插件lazyload.js的使用方法

    lazyload.js是一个基于JQuery的插件,可以用来缓冲加载图片.如果一个网页很长并且有很多图片的话,下载图片就需要很多时间,那么就会影响整个网页的加载速度,而这款延迟加载插件,会通过你的滚动 ...

  6. [JQuery插件系列]-强烈推荐10个非常不错的jQuery工具提示插件

    个非常酷的 jQuery 工具提示(Tooltip)插件,希望大家能喜欢. 1. Pop! 使用 Pop!可以创建简单的下拉菜单!这是一个并不引人注目的 jQuery 插件. 2. BetterTip ...

  7. MongoDB Sort op eration used more than the maximum 33554432 bytes of RAM. Add an index, or speci fy a smaller limit.

    最近在获取mongodb某个集合的数据过程中,在进行排序的过程中报错,具体报错信息如下: Error: error: { , "errmsg" : "Executor e ...

  8. 图解Eclipse或者SpringSource Tool Suite 创建模块化Maven项目

    第一步:Package Explorer里选择右键,新建Maven项目,如果没有在Other里找,还没有确认一下是否安装了Maven插件 第二步:在Wizards向导里可以通过搜索,找到Maven P ...

  9. Asp.net 恢复页面内用户控件内的控件ClientID

    众所周知在Asp.net中如果一个页面添加了一个用户控件(或母版页),那么用户控件内的控件的   ClientID号会被自动添加页面中用户控件的ClientID 即页面中的控件内的控件ClientID ...

  10. Window 下安装 Redis

    Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. 一.Window 下安装 redis https://github.com/MicrosoftArchive/r ...