什么是javascript匿名函数的生命周期?

Wil*_*iam 16 javascript anonymous-function lifetime

如果我在全球范围内写这个:

(function(){})();
Run Code Online (Sandbox Code Playgroud)

是在执行语句后立即执行和销毁语句时创建的匿名函数?

如果我在函数中写这个:

function foo()
{
    var a=1;
    (function(){})();
    a++;
}
Run Code Online (Sandbox Code Playgroud)

匿名函数在foo返回之前是否存在,或者只是在执行该语句期间存在?

t.n*_*ese 22

在这种特殊情况下,大多数引擎会完全消除该功能,因为它不会做任何事情.

但是我们假设函数包含代码并且确实已执行.在这种情况下,该函数将一直存在,可以是编译代码,也可以是字节码,也可以是解释器的AST.

不会一直存在的部分是范围和可能创建的闭包.只有在执行函数或存在对具有特定绑定范围/闭包的函数的引用时,才会存在为该函数和闭包创建的作用域.

因此,组合函数引用+范围将在语句(function(){})();执行时分配,并可在该语句之后释放.但编译后的版本function(){}可能仍然存在于内存中供以后使用.

对于即时编译和优化的引擎,函数甚至可能存在于不同的编译版本中.

现代js引擎的JIT +优化器部分是一个复杂的主题,可以在这里找到v8的粗略描述html5rocks:JavaScript编译:

在V8中,Full编译器在所有代码上运行,并尽快开始执行代码,快速生成好但不是很好的代码.这个编译器几乎没有假设编译时的类型 - 它期望变量类型可以并且将在运行时更改.

与完整的编译器并行,V8使用优化编译器重新编译"热"函数(即,运行多次的函数).[...]在优化编译器中,操作以推测方式内联(直接放置在它们被调用的位置).这加快了执行速度(以内存占用为代价),但也可以实现其他优化.

因此,生成的代码可能与原始代码几乎没有任何相似之处.

因此,使用内联甚至可以完全优化立即调用的函数表达式.

  • 有关V8的信息已经过了约17个月,V8使用了一个字节码解释器,用于一次性或很少使用的代码,因为用Ignition + TurboFan替换了旧的Full-codegen + Crankshaft([详情](https://) v8.dev/blog/launching-ignition-and-turbofan)).(Ignition是字节码解释器.) (2认同)
  • 我也不太确定一次性功能的字节码是不是被抛出,V8团队已经做了很多工作来专注于减少内存消耗; 删除有关永远不能再次调用的函数的所有信息将是不容置疑的结果. (2认同)

T.J*_*der 5

\n

如果我在全局范围内写这个:

\n
\n
\n
(function(){})();\n
Run Code Online (Sandbox Code Playgroud)\n
\n
\n

匿名函数是在语句执行时创建并在语句执行后立即销毁吗?

\n
\n

正如 t.niese 所说,引擎很可能会完全优化该功能。所以我们假设它里面有一些代码:

\n
// At global scope\n(function(){ console.log("Hi there"); })();\n
Run Code Online (Sandbox Code Playgroud)\n

引擎不能保证该代码不会抛出错误(例如,如果您console用其他东西替换),所以我相当确定它不能只是内联它。

\n

现在的答案是:这要看情况。

\n

从语言/规范级别来看,所有代码都在一个编译单元中的所有代码(大致:脚本)在编译单元首次加载时都会被解析。然后,当代码在逐步执行中到达该函数时,就会创建该函数,在创建后执行(这涉及为调用创建执行上下文),并且在完成后立即有资格进行垃圾回收(以及执行上下文)因为没有任何东西可以引用它。但这只是理论/高级规范。

\n

从 JavaScript 引擎的角度来看:

\n
    \n
  • 函数被解析。该解析的结果(字节码或机器代码)将与该函数表达式相关联。这不会等待执行到达函数,而是提前完成(在 V8 [Google Chrome 和 Node.js 中的 Google 引擎] 的后台)。
  • \n
  • 一旦函数被执行,没有其他东西可以引用它:
  • \n
  • 函数对象和与调用它相关的执行上下文都有资格进行GC。何时以及如何发生取决于 JavaScript 引擎。
  • \n
  • 这留下了函数的底层代码,要么是字节码(使用 Ignition 的 V8 的现代版本,可能是其他版本),要么是编译后的机器代码(不知何故,该函数使用得太多,以至于为 TurboFan 进行了编译,或者使用 Full-codegen 的旧版本 V8 , 其他的)。JavaScript 引擎是否可以丢弃字节码或机器代码将取决于引擎。我怀疑引擎会丢弃它们为函数生成的字节/机器代码,如果它们可能再次需要它(例如,对于由新调用创建的新匿名函数foo)。如果foo变得无法访问,也许foo字节/机器代码和匿名函数可能会因为不必要而被丢弃。我不知道引擎是否能做到这一点。一方面,这似乎是唾手可得的成果;另一方面,这似乎是一种不常见的事情,不值得费心。(请记住,这里我们不是在讨论附加到函数实例的代码代码,而是从源生成的代码,该代码在创建实例时附加到实例,并且随着时间的推移可能会附加到多个实例。 )
  • \n
\n

以下是 V8 博客上的几篇文章,读起来很有趣:

\n\n
\n

如果我把它写在一个函数中:

\n
\n
\n
function foo()\n{\n    var a=1;\n    (function(){})();\n    a++;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n
\n

匿名函数在 foo 返回之前一直存在,还是仅在该语句执行期间存在?

\n
\n

让我们再次假设console.log该函数中有一个,并且我是正确的(这我的假设),它依赖于可写全局 ( console) 的事实意味着它不能只是内联。

\n

高级/规范答案是相同的:函数在加载脚本时被解析,在脚本到达时创建,执行,并且在执行完成时有资格进行 GC。但同样,这只是高级概念。

\n

引擎层面的情况可能有所不同:

\n
    \n
  • 该代码将在脚本中的任何代码运行之前被解析。
  • \n
  • 字节码或机器代码可能是在脚本中的任何代码运行之前生成的,尽管我似乎记得V8 博客中有关解析但不立即编译顶级函数内容的内容。不过,如果它不只是在我的脑海里的话,我找不到那篇文章。
  • \n
  • 当执行到达函数时,会为其创建一个函数对象以及执行上下文(除非引擎确定它可以对其进行优化而无需在代码中观察到)。
  • \n
  • 一旦执行结束,函数对象和执行上下文就可以进行 GC。(它们很可能已经在堆栈上,使得foo返回时 GC 变得微不足道。)
  • \n
  • 不过,底层代码会保留在内存中以便再次使用(如果使用得足够频繁,则会进行优化)。
  • \n
\n