
可能应该从以下事实开始:PHP中的匿名函数(闭包)不是函数,而是
Closure类的对象。 实际上,本文可能已经完成,但是如果有人对这些细节感兴趣,欢迎关注。
为了没有根据:
$func = function (){}; var_dump($func); --------- object(Closure)
展望未来,我会说这并不是一个普通的对象。 让我们弄清楚。
例如,这样的代码
$func = function (){ echo 'Hello world!'; }; $func();
编译成这样的一组操作码:
line
带有函数体描述的代码块对我们来说并不是特别有趣,但是在第一个代码块中,有两个有趣的操作码:
DECLARE_LAMBDA_FUNCTION和
INIT_DYNAMIC_CALL 。 让我们从第二个开始。
INIT_DYNAMIC_CALL
当编译器看到变量或数组上的函数调用时,将使用此操作码。 即
$variable(); ['ClassName', 'staticMethod']();
这不是一些特定于闭包的唯一操作码。 通过调用
__invoke()方法,此语法也可用于对象,对于包含函数名(
$ a ='funcName'; $ a(); )的字符串变量,以及在其中包含类名和静态方法的数组,此语法也适用。
在闭包的情况下,我们感兴趣的是使用逻辑上的对象调用变量。
深入研究处理此操作码的VM代码,我们进入
zend_init_dynamic_call_object函数,在其中我们将看到以下内容(切片):
zend_execute_data *zend_init_dynamic_call_object(zend_object *function, uint32_t num_args) { zend_function *fbc; zend_class_entry *called_scope; zend_object *object; ... if (EXPECTED(function->handlers->get_closure) && EXPECTED(function->handlers->get_closure(function, &called_scope, &fbc, &object) == SUCCESS)) { ... } else { zend_throw_error(NULL, "Function name must be a string"); return NULL; } ... }
有趣的是,就VM而言,熟悉的__invoke方法调用是尝试调用Closure- get_closure的尝试。实际上,在这一点上,区别开始于处理对匿名函数的调用和常规对象的
__invoke方法。
在PHP中,每个对象都有一组定义其实用程序和magic方法的不同处理程序。
标准集看起来像这样 ZEND_API const zend_object_handlers std_object_handlers = { 0, zend_object_std_dtor, zend_objects_destroy_object, zend_objects_clone_obj, zend_std_read_property, zend_std_write_property, zend_std_read_dimension, zend_std_write_dimension, zend_std_get_property_ptr_ptr, NULL, NULL, zend_std_has_property, zend_std_unset_property, zend_std_has_dimension, zend_std_unset_dimension, zend_std_get_properties, zend_std_get_method, zend_std_get_constructor, zend_std_get_class_name, zend_std_compare_objects, zend_std_cast_object_tostring, NULL, zend_std_get_debug_info, zend_std_get_closure, zend_std_get_gc, NULL, NULL, NULL, };
现在,我们对
get_closure处理程序感兴趣。 对于常规对象,它指向
zend_std_get_closure函数,该函数检查是否为该对象定义了
__invoke函数,并返回指向该对象的指针或错误。 但是对于实现匿名函数的
Closure类,在此处理程序数组中,几乎所有实用程序函数都被重新定义,包括那些控制生命周期的实用程序函数。 即 虽然对于用户来说,它看起来像一个普通的对象,但实际上,它是具有超能力的变体:)
为类Closure的对象注册处理程序 void zend_register_closure_ce(void) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Closure", closure_functions); zend_ce_closure = zend_register_internal_class(&ce); zend_ce_closure->ce_flags |= ZEND_ACC_FINAL; zend_ce_closure->create_object = zend_closure_new; zend_ce_closure->serialize = zend_class_serialize_deny; zend_ce_closure->unserialize = zend_class_unserialize_deny; memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers)); closure_handlers.free_obj = zend_closure_free_storage; closure_handlers.get_constructor = zend_closure_get_constructor; closure_handlers.get_method = zend_closure_get_method; closure_handlers.write_property = zend_closure_write_property; closure_handlers.read_property = zend_closure_read_property; closure_handlers.get_property_ptr_ptr = zend_closure_get_property_ptr_ptr; closure_handlers.has_property = zend_closure_has_property; closure_handlers.unset_property = zend_closure_unset_property; closure_handlers.compare_objects = zend_closure_compare_objects; closure_handlers.clone_obj = zend_closure_clone; closure_handlers.get_debug_info = zend_closure_get_debug_info; closure_handlers.get_closure = zend_closure_get_closure; closure_handlers.get_gc = zend_closure_get_gc; }
该手册说:
除了此处描述的方法外,此类还具有__invoke方法。 仅在与实现魔术调用的其他类兼容时才需要此方法,因为在调用函数时不使用此方法。
这是真的。 闭包的
get_closure函数不会返回
__invoke ,而是从中创建闭包的函数。
您可以自己更详细地研究资源-文件
zend_closure.c ,我们将继续进行下一个操作码。
DECLARE_LAMBDA_FUNCTION
但这是专门用于电路的操作码,不再与任何东西一起使用。 在处理器的内部,有三个主要操作:
- 寻找指向已编译函数的指针,这将是闭包的本质。
- 定义了创建闭包的上下文(换句话说, 此 )。
- 基于前两点, 创建了Closure类的对象。
在这个地方,并不是一个令人愉快的消息开始。
那么匿名函数怎么了?
创建闭包比创建普通对象要困难得多。 创建对象的标准机制不仅被称为,而且还添加了一定数量的逻辑,其中最不愉快的是将函数的整个操作码数组复制到闭包的主体中。 它本身并没有那么可怕,但是直到您开始“错误地”使用它时才如此。
为了确切地了解问题在哪里等待,我们将分析创建闭包时的情况。
重新创建关闭:
a)在DECLARE_LAMBDA_FUNCTION
操作码的每次处理中。
直观地-闭包看起来很好的情况就是这样,但实际上在每次循环迭代时都会创建一个新的闭包对象。
foreach($values as $value){ doSomeStuff($value, function($args) { closureBody }); }
b)每次
调用 bind和
bindTo方法时:
在这里,每次迭代也将重新创建闭包。
$closure = function($args) { closureBody }; foreach($objects as $object){ $closure->bindTo($object); $object->doSomeStuff($closure); }
c)每次
调用调用方法时(如果将生成器用作函数)。 如果不是生成器,而是普通函数,则仅执行复制操作码数组的部分。 这样的事情。
结论
如果性能对您不重要,不惜一切代价,那么匿名功能将很方便且令人愉快。 如果重要,那么可能不值得。
无论如何,现在您知道,如果未正确准备闭包和循环,它们就是这样的组合。
感谢您的关注!