JavaScript匿名函数的寿命是多长?

时间:2018-10-15 07:57:18

标签: javascript anonymous-function lifetime

如果我在全局范围内编写此代码:

(function(){})();

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

如果我在函数中编写此代码:

function foo()
{
    var a=1;
    (function(){})();
    a++;
}

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

2 个答案:

答案 0 :(得分:22)

在这种特殊情况下,大多数引擎会完全不优化该功能,因为它不会执行任何操作。

但是让我们假设函数包含代码并且确实已执行。在这种情况下,该功能将一直存在,无论是作为编译代码,字节码还是解释器的AST。

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

因此,函数引用+作用域组合将在执行语句(function(){})();时分配,并且可以在该语句后释放。但是function(){}的编译版本可能仍存在于内存中以备后用。

对于只进行及时编译和优化的引擎,甚至在不同的编译版本中都可能存在一个函数。

现代js引擎的JIT + optimizer部分是一个复杂的主题,可以在html5rocks: JavaScript Compilation处找到v8的粗略描述:

  

在V8中,Full编译器在所有代码上运行,并尽快开始执行代码,从而迅速生成好的但不是好的代码。该编译器在编译时几乎不假设类型,它希望变量类型可以并且将在运行时更改。

     

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

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

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

答案 1 :(得分:4)

  

如果我在全局范围内编写此代码:

(function(){})();
     

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

正如p.niese所说,引擎完全可以优化该功能的可能性很大。因此,我们假设其中包含一些代码:

// At global scope
(function(){ console.log("Hi there"); })();

引擎无法保证该代码不会引发错误(例如,如果您将console替换为其他内容),那么我敢肯定,它不能只是内联。 / p>

现在的答案是:这取决于。

从语言/规范级别开始,当第一次加载编译单元时,将解析编译单元(大致为:脚本)中的所有代码。然后,当代码在逐步执行中到达该函数时创建该函数,在创建后执行该函数(这涉及为调用创建执行上下文),并在完成后立即有资格进行垃圾回收(连同执行上下文)因为没有任何参考。但这仅仅是理论/高级规范。

从JavaScript引擎的角度来看:

  • 在运行任何代码之前,已对函数进行解析。解析的结果(字节码或机器码)将与该函数表达式关联。这不等待执行完成就可以完成功能,它已经尽早完成了(在V8(Chrome和Node.js中Google的引擎)的后台)。
  • 该函数一旦执行,别无他法:
    • 函数 object 和与其调用相关的执行上下文都可以使用GC。何时以及如何发生取决于JavaScript引擎。
    • 其中保留了函数的基础 code ,或者是字节码(使用Ignition的V8的现代版本,也可能是其他版本)或编译的机器代码(使用Full-codegen的V8的较旧版本)。然后,JavaScript引擎能否丢弃该字节码或机器码将取决于引擎。我对引擎丢弃字节码或机器码的功能一无所知,这些字节码或机器码无法再使用。我确实知道V8团队在减少内存影响上花费了很多时间,并且把代码扔掉似乎是轻而易举的事情。 :-)而且他们专门花费时间来优化一次性启动代码的影响。

以下是V8博客上的几篇有趣的文章:

  

如果我在函数中编写此代码:

function foo()
{
    var a=1;
    (function(){})();
    a++;
}
     

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

让我们再次假设该函数中有一个console.log,并且我是对的(对我来说这是 的假设)是事实,它依赖于可写的全局变量({ {1}})意味着它不能仅内联。

高级/规范的答案是相同的:该函数在脚本加载时解析,在到达脚本时创建,执行并在执行完毕后才有资格使用GC。但这又只是一个高级概念。

引擎级别的情况可能有所不同:

  • 在脚本中的任何代码运行之前,将对代码进行解析
  • 在脚本中的任何代码运行之前,很可能会生成字节码或机器代码,尽管我似乎想起了V8博客中的某事,但有关解析但不是立即编译顶级函数的内容。但是,即使不是我脑海中也找不到那篇文章。
  • 当执行到达函数时,将为其创建函数 object 以及执行上下文(除非引擎确定在没有代码可观察的情况下,它可以对其进行优化)。
  • 执行结束后,函数 object 和该执行上下文才有资格使用GC。 (它们很可能在堆栈中,当console返回时,GC变得微不足道了。)
  • 不过,底层代码会停留在内存中以便再次使用(并且如果经常使用,则会进行优化)。