如果我在全局作用域中写下以下代码:
(function(){})();
当语句执行时创建的匿名函数,执行完语句后立即销毁,对吗?
如果我把这个写在一个函数里:
function foo()
{
var a=1;
(function(){})();
a++;
}
匿名函数是在foo函数返回之前存在还是仅在执行该语句时存在?
如果我在全局作用域中写下以下代码:
(function(){})();
当语句执行时创建的匿名函数,执行完语句后立即销毁,对吗?
如果我把这个写在一个函数里:
function foo()
{
var a=1;
(function(){})();
a++;
}
匿名函数是在foo函数返回之前存在还是仅在执行该语句时存在?
在这种情况下,大多数引擎都会完全优化掉该函数,因为它没有任何作用。
但是假设该函数包含代码并确实被执行。在这种情况下,该函数将一直存在,无论是作为已编译的代码、字节码还是解释器的AST。
不会一直存在的部分是作用域和可能创建的闭包。为该函数创建的作用域和闭包仅在函数执行或具有特定绑定作用域/闭包的函数引用存在时才存在。
因此,组合 函数引用+作用域 将在执行语句 (function(){})();
时分配,并可以在该语句之后释放。但是编译后的 function(){}
版本可能仍然存在于内存中以供以后使用。
对于进行即时编译和优化的引擎,一个函数甚至可能存在于不同的编译版本中。
现代js引擎的JIT+优化器部分是一个复杂的主题,可以在这里找到v8的粗略描述:html5rocks:JavaScript Compilation:
在V8中,全编译器将运行所有代码,并尽快开始执行代码,快速生成良好但不是最佳的代码。该编译器在编译时几乎不假设类型-它预计变量类型可能会在运行时更改。与全编译器并行,V8使用优化编译器重新编译“热”函数(即运行多次的函数)。[...] 在优化编译器中,操作被推测地内联(直接放置在调用它们的位置)。这加快了执行速度(以内存占用为代价),但也启用了其他优化。
因此,生成的代码可能与原始代码几乎没有相似之处。
因此,立即调用的函数表达式甚至可以使用内联完全优化掉。
(function(){})();
当语句被执行时,创建匿名函数并在语句执行后立即销毁?
正如t.niese所说,引擎很有可能会完全优化掉这个函数。因此,让我们假设它包含一些代码:
// At global scope
(function(){ console.log("Hi there"); })();
现在的答案是:视情况而定。
从语言/规范层面上来说,在编译单元(大致上指脚本)中的所有代码都会在第一次加载编译单元时进行解析。然后,在代码逐步执行到该函数时创建函数,执行并立即合格以进行垃圾回收(连同执行环境)。但这只是理论/高级规范。
从JavaScript引擎的角度来看:
foo
的新调用创建的新匿名函数)。如果 foo 变得不可访问,则可以将 foo 的字节码/机器码和匿名函数的字节码/机器码视为不必要而被丢弃。我不知道引擎是否会这样做。一方面,它似乎是低垂的果实;另一方面,它似乎是如此罕见,以至于不值得费心。 (请记住,在这里,我们谈论的不是附加到函数实例的代码,而是从创建实例时附加到实例的源代码产生的代码,并且随着时间的推移可能会附加到多个实例。)
以下是V8博客上的几篇有趣的文章:
- 2018年3月26日:后台编译
- 2017年5月15日:启动Ignition和TurboFan
- 2016年8月23日:启动Ignition解释器 (某些信息已过时并在2017年5月的文章中进行了更新)
- Ignition (相对于上述2017年5月文章,部分Ignition信息似乎略有过时)
如果我将这写在一个函数中:
function foo()
{
var a=1;
(function(){})();
a++;
}
匿名函数存在于 foo 函数运行期间还是只在执行那条语句期间?假设该函数中有一个 console.log,我认为它依赖于一个全局可写的变量(console),这就意味着它不能被内联。从高层次的概念上讲,答案是一样的:当脚本加载时,该函数被解析,到达时创建,执行,并在完成执行后进行垃圾回收。但这只是高层次的概念。在引擎层面,情况可能会有所不同:
- 代码将在脚本中的任何代码之前进行解析。
- 字节码或机器码可能会在脚本中的任何代码之前生成,尽管我似乎记得来自 V8 博客的某些内容关于解析而不是立即编译顶级函数的内容。不过如果不仅仅是我想象中的话,我找不到那篇文章。
- 当执行到该函数时,会为其创建一个函数对象以及执行上下文(除非引擎确信它可以优化掉而不在代码中观察到)。
- 执行结束后,函数对象和执行上下文都可以进行垃圾回收。(它们很可能已经在堆栈上,所以当 foo 返回时 GC 很容易。)
- 然而,底层代码仍然留存在内存中以便再次使用(如果经常使用,将进行优化)。
foo
可以再次调用,则匿名函数肯定可以再次到达。我希望SpiderMonkey、V8和JavaScriptCore知道这一点,并且不会垃圾回收这些函数。如果不是这样,那么最好的做法就是不要在频繁调用的代码中使用匿名函数。 - Inigofoo
,它就是一个不同的匿名函数,而不是同一个函数。但是这条评论让我想稍微修改一下关于字节码/机器码的段落。 - T.J. Crowder