web-dev-qa-db-ja.com

javascript無名関数の存続期間はどれくらいですか?

これをグローバルスコープで書くと:

(function(){})();

ステートメントの実行時に無名関数が作成され、ステートメントの実行直後に破棄されますか?

これを関数に書くと:

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

匿名関数はfooが戻るまで存在しますか、それともそのステートメントの実行中に存在しますか?

16
William

この特定のケースでは、ほとんどのエンジンは何もしないため、その機能を完全に最適化します。

しかし、関数にコードが含まれていて、実際に実行されていると仮定しましょう。この場合、関数は、コンパイルされたコード、バイトコード、またはインタープリターのAST)のいずれかとして常に存在します。

常に存在するわけではない部分は、スコープと作成される可能性のあるクロージャーです。その関数とクロージャ用に作成されたスコープは、関数が実行されるか、特定のバインドされたスコープ/クロージャを持つ関数への参照が存在する場合にのみ存在します。

したがって、組み合わせ関数参照+スコープは、ステートメント_(function(){})();_の実行時に割り当てられ、そのステートメントの後に解放できます。ただし、コンパイルされたバージョンのfunction(){}は、後で使用するためにメモリに残っている可能性があります。

コンパイルと最適化がちょうど間に合うエンジンの場合、関数はコンパイルされたさまざまなバージョンに存在することさえあります。

最新のjsエンジンのJIT +オプティマイザー部分は複雑なトピックです。v8の大まかな説明はここにあります html5rocks:JavaScriptコンパイル

V8では、Fullコンパイラはすべてのコードで実行され、できるだけ早くコードの実行を開始し、優れているが優れていないコードをすばやく生成します。このコンパイラは、コンパイル時に型についてほとんど何も想定していません。変数の型は実行時に変更される可能性があり、変更されることを想定しています。

完全なコンパイラと並行して、V8は最適化コンパイラを使用して「ホット」関数(つまり、何度も実行される関数)を再コンパイルします。 [...]最適化コンパイラでは、操作は投機的にインライン化されます(呼び出された場所に直接配置されます)。これにより、実行が高速化されますが(メモリフットプリントが犠牲になります)、他の最適化も可能になります。

したがって、生成されたコードは元のコードとほとんど類似していない可能性があります。

したがって、即時に呼び出される関数式は、インライン化を使用して完全に最適化される場合もあります。

22
t.niese

これをグローバルスコープで書くと:

(function(){})();

ステートメントの実行時に無名関数が作成され、ステートメントの実行直後に破棄されますか?

T.nieseが言ったように、エンジンがその機能を完全に最適化する可能性があります。それで、それにいくつかのコードがあると仮定しましょう:

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

エンジンは、そのコードがエラーをスローしないことを保証できないため(たとえば、consoleを別のものに置き換えた場合)、それをインライン化することはできないと確信しています。

今の答えは:それは異なります。

言語/仕様レベルから、コンパイルユニット(大まかに:スクリプト)内のすべてのコードは、コンパイルユニットが最初にロードされるときに解析されます。次に、その関数は、コードが段階的な実行で到達すると作成され、作成後に実行され(呼び出しの実行コンテキストの作成が含まれます)、実行されるとすぐにガベージコレクションの対象になります(実行コンテキストとともに)何もそれへの参照を持っていないので。しかし、それは単なる理論/高レベルの仕様です。

JavaScriptエンジンの観点から:

  • この関数は、コードが実行される前に解析されます。その解析の結果(バイトコードまたはマシンコード)は、その関数式に関連付けられます。これは、実行が関数に到達するのを待つのではなく、早期に実行されます(V8のバックグラウンド[GoogleのエンジンChromeおよびNode.js]))。
  • 関数が実行され、他の誰もそれを参照できないようになると:
    • 関数objectと、それを呼び出すことに関連する実行コンテキストは、どちらもGCの対象です。それがいつどのように発生するかは、JavaScriptエンジンによって異なります。
    • これにより、関数の基礎となるコードが残り、バイトコード(Ignitionを使用するV8の最新バージョンなど)またはコンパイルされたマシンコード(Full-codegenを使用するV8の古いバージョンなど)が残ります。 JavaScriptエンジンがそのバイトコードまたはマシンコードを破棄できるかどうかは、エンジンによって異なります。二度と到達できない関数のバイトコードやマシンコードをエンジンが破棄するという特定の知識はありません。私は、V8チームがメモリへの影響の削減に多くの時間を費やしており、そのコードを投げることは簡単な成果のように思えることを知っています。 :-)そして、彼らは特に、1回限りのスタートアップコードの影響を最適化することに時間を費やしてきました。

興味深い読み物となるV8ブログの記事をいくつか紹介します。

これを関数で書くと:

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

匿名関数はfooが戻るまで存在しますか、それともそのステートメントの実行中に存在しますか?

その関数にconsole.logがあり、それが書き込み可能なグローバルに依存しているという事実が正しいと仮定しましょう(私の側の仮定です) (console)は、単にインライン化できないことを意味します。

高レベル/仕様の答えは同じです。関数は、スクリプトがロードされるときに解析され、スクリプトに到達して実行されると作成され、実行が完了するとGCの対象になります。しかし、繰り返しになりますが、それは単なる高レベルの概念です。

おそらくエンジンレベルでは状況が異なります。

  • スクリプト内のコードが実行される前に、コードは解析されます
  • バイトコードまたはマシンコードは、スクリプト内のコードが実行される前に生成される可能性がありますが、V8ブログから解析について何かを思い出しているようですが、トップレベルの関数の内容をすぐにはコンパイルしていません。しかし、それが私の頭の中だけではなかった場合、私はその記事を見つけることができません。
  • 実行が関数に到達すると、その関数objectが実行コンテキストとともに作成されます(エンジンがコードで観察できなくてもそれを最適化できると確信している場合を除く)。
  • 実行が終了すると、関数objectとその実行コンテキストがGCの対象になります。 (それらはスタック上にあった可能性があり、fooが戻ったときにGCを簡単にします。)
  • ただし、基になるコードは、再度使用するために(そして、十分な頻度で使用する場合は最適化するために)メモリ内に留まります。
4
T.J. Crowder