呼び出しが完了すると、その呼び出しfoo
中に作成されたものはすべてガベージ コレクション (GC) の対象になります。これは、呼び出し中に作成されたものを保持しているコードがコード内にないためです。より興味深い質問は、foo
返さ れた場合に何が起こるかということです (を呼び出した結果の数値bar
ではなく、関数)。bar()
bar
しかし、あなたが持っているコードでは、呼び出し時に何が起こるかの理論は次のとおりです (仕様の§10.4.3foo
で定義されています)。
エンジンは、最初はその特定の呼び出しのレキシカル環境と変数環境である新しい宣言環境を作成します(通常、これらは分離されません。キーワードで分離できますが、ほとんどの人はそれを使用しません)。その宣言型環境には、それに関連付けられたバインディング オブジェクトがあります。foo
with
への宣言された引数foo
、 name 、 with で宣言されたfoo
変数、関数宣言を介して宣言された関数の名前、およびその他のいくつかは、(定義された順序で) そのバインディング オブジェクトのプロパティとして作成されます (詳細は§10.5を参照) 。 .foo
var
bar
関数を作成するプロセス( §13.2で説明) は、呼び出しのレキシカル環境を関数のプロパティとしてアタッチfoo
しbar
ます[[Scope]]
(コードで使用できるリテラル名ではなく、仕様で使用される名前です)。
x
バインディング オブジェクト (x
変数など)のプロパティは値を取得します10
。
への呼び出しbar
は、変数を使用してまったく新しい宣言環境などを作成しy
ます。新しい環境のバインディング オブジェクトには、それが作成された環境のバインディング オブジェクトへのリンクがあります。その環境は、外部レキシカル環境参照としてbar
の[[Scope]]
プロパティを取得します。
y
最も内側のバインディング オブジェクトのプロパティが値を取得します20
。
式x + y
が評価されます。
x
エンジンは、その値を取得するために解決を試みます。最初に、最も内側のバインディング オブジェクトを調べて、 というプロパティがあるかどうかを確認しますx
が、ありません。
エンジンは、現在の外部レキシカル環境に移動して、バインディング オブジェクトにプロパティがあるかどうかを確認します。x
そのため、エンジンはプロパティの値を読み取り、それを式で使用します。
y
エンジンは、その値を取得するために解決を試みます。最初に、最も内側のバインディング オブジェクトを調べて、 というプロパティがあるかどうかを確認しますy
。そのため、エンジンはその値を式に使用します。
エンジンは に を追加20
して式を完成させ10
、結果をスタックにプッシュしてから を返しますbar
。
この時点で、呼び出しの環境とバインディング オブジェクトbar
を GC 経由で再利用できます。
エンジンは から戻り値を受け取り、bar
それをスタックにプッシュし、 から戻りますfoo
。
この時点で、呼び出しの環境とバインディング オブジェクトfoo
を GC 経由で再利用できます。
コードはconsole.log
結果を呼び出します。(詳細は省略)
したがって、理論的には、永続的なメモリへの影響はありません。環境とそのバインディング オブジェクトは投げることができます。
実際、最新の JavaScript エンジンは非常にスマートで、特定のオブジェクトの割り当てにスタックを使用するため、GC を呼び出してこれらの環境とバインディング オブジェクトを再利用する必要はありません。(しかし、読み続けてください。)
さて、foo
次のようになったとします。
function foo() {
var x = 10;
function bar() {
var y = 20;
return x + y;
}
return bar;
}
そして、これを行いました:
var b = foo();
ここで、foo
への参照を返しますbar
(呼び出さずに)。
上記の手順 1 ~ 4 は変更されていませんが、 を呼び出す 代わりに、それへの参照bar
をfoo
返します。つまり、呼び出しによって作成された環境とバインディング オブジェクトfoo
はGC の対象外です。bar
その呼び出し中に作成された関数にはそれらへの参照があり、(b
変数を介して) その関数への参照があるためです。したがって、その時点で理論的には、次のようなものがヒープに存在します。
+-----+ +-------------+
| | b |---->| 関数 |
+-----+ +-------------+
| | 名前: "バー" | +----------------+
| | [[スコープ]] |---->| 環境 |
+-------------+ +----------------+ +-------+
| | バインディング オブジェクト |---->| ×:10 |
+----+ +-------+
したがって、最新のエンジンがこれらのオブジェクトをスタックに (場合によっては) 適切に割り当てることができる場合、foo
返された後もそれらのオブジェクトが存在し続けることができるでしょうか? 確認するには、個々のエンジンの内部を掘り下げる必要があります。状況が可能かどうかを確認するために静的分析を実行し、バインディング オブジェクトが生き残ることができる場合は、最初からヒープ割り当てを使用する人もいるでしょう。生き残るべきものをいつfoo
返すかを判断し、それらのものをスタックからヒープにコピーするだけの人もいます。または [本当にスマートなコンパイラ ライターをここに挿入]。一部のエンジンは、参照できる可能性があるものだけを保持するほど十分にスマートな場合があります (そのため、foo
によってまったく参照されない変数が にある場合bar
、それらはバインディング オブジェクトから削除される可能性があります)。高レベル、仕様はそれが見えることを必要とします上記の構造がメモリに保持されているように、コードでできることは、それが起こったことではないことを証明できません。
次に を呼び出すとb
、上記の手順を取得して、手順 5 から 10 を実行しますが、b
戻ると、上記の構造が引き続き存在します。
これが JavaScriptクロージャの仕組みです。