匿名函数在编程语言中出现的比较早,最早出现在Lisp语言中,随后很多的编程语言都开始有这个功能了, 目前使用比较广泛的Javascript以及C#,PHP直到5.3才开始真正支持匿名函数, C++的新标准C++0x也开始支持了。
匿名函数是一类不需要指定标示符,而又可以被调用的函数或子例程,匿名函数可以方便的作为参数传递给其他函数, 最常见应用是作为回调函数。
闭包(Closure)
说到匿名函数,就不得不提到闭包了,闭包是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数, 这个被应用的自由变量将和这个函数一同存在,即使离开了创建它的环境也一样,所以闭包也可认为是有函数和与其相关引用组合而成的实体。 在一些语言中,在函数内定义另一个函数的时候,如果内部函数引用到外部函数的变量,则可能产生闭包。在运行外部函数时, 一个闭包就形成了。
这个词和匿名函数很容易被混用,其实这是两个不同的概念,这可能是因为很多语言实现匿名函数的时候允许形成闭包。
使用create_function()创建”匿名”函数
前面提到PHP5.3中才才开始正式支持匿名函数,说到这里可能会有细心读者有意见了,因为有个函数是可以生成匿名函数的: create_function函数, 在手册里可以查到这个函数在PHP4.1和PHP5中就有了,这个函数通常也能作为匿名回调函数使用, 例如如下:
<?php $array = array(1, 2, 3, 4); array_walk($array, create_function('$value', 'echo $value')); ?>
这段代码只是将数组中的值依次输出,当然也能做更多的事情。 那为什么这不算真正的匿名函数呢, 我们先看看这个函数的返回值,这个函数返回一个字符串, 通常我们可以像下面这样调用一个函数:
<?php function a() { echo 'function a'; } $a = 'a'; $a(); ?>
我们在实现回调函数的时候也可以采用这样的方式,例如:
<?php function do_something($callback) { // doing # ... // done $callback(); } ?>
这样就能实现在函数do_something()执行完成之后调用$callback指定的函数。回到create_function函数的返回值: 函数返回一个唯一的字符串函数名, 出现错误的话则返回FALSE。这么说这个函数也只是动态的创建了一个函数,而这个函数是有函数名的,也就是说,其实这并不是匿名的。 只是创建了一个全局唯一的函数而已。
<?php $func = create_function('', 'echo "Function created dynamic";'); echo $func; // lambda_1 $func(); // Function created dynamic $my_func = 'lambda_1'; $my_func(); // 不存在这个函数 lambda_1(); // 不存在这个函数 ?>
上面这段代码的前面很好理解,create_function就是这么用的,后面指定函数名调用却失败了,这就有些不好理解了, php是怎么保证这个函数是全局唯一的? lambda_1看起来也是一个很普通的函数明,如果我们先定义一个叫做lambda_1的函数呢? 这里函数的返回字符串会是lambda_2,它在创建函数的时候会检查是否这个函数是否存在知道找到合适的函数名, 但如果我们在create_function之后定义一个叫做lambda_1的函数会怎么样呢? 这样就出现函数重复定义的问题了, 这样的实现恐怕不是最好的方法,实际上如果你真的定义了名为lambda_1的函数也是不会出现我所说的问题的。这究竟是怎么回事呢? 上面代码的倒数2两行也说明了这个问题,实际上并没有定义名为lambda_1的函数。
也就是说我们的lambda_1和create_function返回的lambda_1并不是一样的!? 怎么会这样呢? 那只能说明我们没有看到实质, 只看到了表面,表面是我们在echo的时候输出了lambda_1,而我们的lambda_1是我们自己敲入的. 我们还是使用debug_zval_dump函数来看看吧。
<?php $func = create_function('', 'echo "Hello";'); $my_func_name = 'lambda_1'; debug_zval_dump($func); // string(9) "lambda_1" refcount(2) debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2) ?>
看出来了吧,他们的长度居然不一样,长度不一样,所以我们调用的函数当然是不存在的, 我们还是直接看看create_function函数到底都做了些什么吧。 该实现见: $PHP_SRC/Zend/zend_builtin_functions.c
#define LAMBDA_TEMP_FUNCNAME "__lambda_func" ZEND_FUNCTION(create_function) { // ... 省去无关代码 function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG); function_name[0] = ' '; // <--- 这里 do { function_name_length = 1 + sprintf(function_name + 1, "lambda_%d", ++EG(lambda_count)); } while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE); zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)); RETURN_STRINGL(function_name, function_name_length, 0); }
该函数在定义了一个函数之后,给函数起了个名字,它将函数名的第一个字符变为了’