2

関数があるとしましょう:

function foo() {
    var x = 10;         
    function bar() {
        var y = 20; 
        return x + y;   
    }
    return bar();
}

console.log(foo());

これは、メモリ モデルではど​​のように見えるでしょうか。これまでのところ、これはスタック上でどのように見えると思いますか?

TOP OF THE STACK 
-------------- 
bar()
y = 20
return x + 20
-------------- 
foo()
x= 10
bar()
--------------
BOTTOM OF THE STACK 

レキシカル スコープはどのように見えますか? foo()ヒープ上ですか?またはbar()へのポインタがありますfoo()か?

4

1 に答える 1

2

呼び出し完了すると、その呼び出しfoo中に作成されたものはすべてガベージ コレクション (GC) の対象になります。これは、呼び出し中に作成されたものを保持しているコードがコード内にないためです。より興味深い質問は、foo 返さ れた場合に何が起こるかということです (を呼び出した結果の数値barではなく、関数)。bar()bar

しかし、あなたが持っているコードでは、呼び出し時に何が起こるかの理論は次のとおりです (仕様の§10.4.3fooで定義されています)。

  1. エンジンは、最初はその特定の呼び出しのレキシカル環境変数環境である新しい宣言環境を作成します(通常、これらは分離されません。キーワードで分離できますが、ほとんどの人はそれを使用しません)。その宣言型環境には、それに関連付けられたバインディング オブジェクトがあります。foowith

  2. への宣言された引数foo、 name 、 with で宣言されたfoo変数、関数宣言を介して宣言された関数の名前、およびその他のいくつかは、(定義された順序で) そのバインディング オブジェクトのプロパティとして作成されます (詳細は§10.5を参照) 。 .foovar

  3. bar関数を作成するプロセス( §13.2で説明) は、呼び出しのレキシカル環境を関数のプロパティとしてアタッチfoobarます[[Scope]](コードで使用できるリテラル名ではなく、仕様で使用される名前です)。

  4. xバインディング オブジェクト (x変数など)のプロパティは値を取得します10

  5. への呼び出しbarは、変数を使用してまったく新しい宣言環境などを作成しyます。新しい環境のバインディング オブジェクトには、それが作成された環境のバインディング オブジェクトへのリンクがあります。その環境は、外部レキシカル環境参照としてbar[[Scope]]プロパティを取得します。

  6. y最も内側のバインディング オブジェクトのプロパティが値を取得します20

  7. x + yが評価されます。

    1. xエンジンは、その値を取得するために解決を試みます。最初に、最も内側のバインディング オブジェクトを調べて、 というプロパティがあるかどうかを確認しますxが、ありません。

    2. エンジンは、現在の外部レキシカル環境に移動して、バインディング オブジェクトにプロパティがあるかどうかを確認します。xそのため、エンジンはプロパティの値を読み取り、それを式で使用します。

    3. yエンジンは、その値を取得するために解決を試みます。最初に、最も内側のバインディング オブジェクトを調べて、 というプロパティがあるかどうかを確認しますy。そのため、エンジンはその値を式に使用します。

  8. エンジンは に を追加20して式を完成させ10、結果をスタックにプッシュしてから を返しますbar

  9. この時点で、呼び出しの環境とバインディング オブジェクトbarを GC 経由で再利用できます。

  10. エンジンは から戻り値を受け取り、barそれをスタックにプッシュし、 から戻りますfoo

  11. この時点で、呼び出しの環境とバインディング オブジェクトfooを GC 経由で再利用できます。

  12. コードは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 は変更されていませんが、 を呼び出す 代わりに、それへの参照barfoo返します。つまり、呼び出しによって作成された環境とバインディング オブジェクトfoo GC の対象外です。barその呼び出し中に作成された関数にはそれらへの参照があり、(b変数を介して) その関数への参照があるためです。したがって、その時点で理論的には、次のようなものがヒープに存在します。

+-----+ +-------------+
| | b |---->| 関数 |
+-----+ +-------------+
            | | 名前: "バー" | +----------------+
            | | [[スコープ]] |---->| 環境 |
            +-------------+ +----------------+ +-------+
                                | | バインディング オブジェクト |---->| ×:10 |
                                +----+ +-------+

したがって、最新のエンジンがこれらのオブジェクトをスタックに (場合によっては) 適切に割り当てることができる場合、foo返された後もそれらのオブジェクトが存在し続けることができるでしょうか? 確認するには、個々のエンジンの内部を掘り下げる必要があります。状況が可能かどうかを確認するために静的分析を実行し、バインディング オブジェクトが生き残ることができる場合は、最初からヒープ割り当てを使用する人もいるでしょう。生き残るべきものをいつfoo返すかを判断し、それらのものをスタックからヒープにコピーするだけの人もいます。または [本当にスマートなコンパイラ ライターをここに挿入]。一部のエンジンは、参照できる可能性があるものだけを保持するほど十分にスマートな場合があります (そのため、fooによってまったく参照されない変数が にある場合bar、それらはバインディング オブジェクトから削除される可能性があります)。高レベル、仕様はそれが見えることを必要とします上記の構造がメモリに保持されているように、コードでできることは、それが起こったことではないことを証明できません。

次に を呼び出すとb、上記の手順を取得して、手順 5 から 10 を実行しますが、b戻ると、上記の構造が引き続き存在します。

これが JavaScriptクロージャの仕組みです。

于 2014-10-01T21:21:52.500 に答える