8

JavaScript クロージャーについて読んでいます。私は実行コンテキストレキシカル環境がどのように維持されるか、そしてレキシカル スコーピングに非常に精通しています。

JavaScript のクロージャがどのように作成され維持れているか知りたいです。実際にどのように機能しているかを知らずに、そのような重要な概念を理解するのが難しい場合があります。ウィキペディアによると、閉鎖は

関数または関数への参照と、参照環境 (その関数の非ローカル変数 (自由変数とも呼ばれます) への参照を格納するテーブル) を組み合わせたものです。

しかし、私の質問は、ECMA 仕様に従って、クロージャーが作成および維持される方法を知りたいということです。閉鎖理論の高レベルの説明を探しているわけではありません。回答でECMA仕様を参照してください。

注: 回答が ECMA 仕様を使用した閉鎖を説明していない限り、これを重複と見なさないでください。繰り返しますが、ウィキペディアを引用して例を挙げている人には興味がありません。JavaScript がどのようにこれを行っているかを完全に理解したいのです。(私はSO に関するこの質問に精通しています)。

4

1 に答える 1

7

ウィキペディアの定義によると、質問で述べたように、クロージャーは

関数または関数への参照と、参照環境 (その関数の非ローカル変数 (自由変数とも呼ばれます) への参照を格納するテーブル) を組み合わせたものです。

実行コンテキストとレキシカル環境がどのように維持されるかについての理解は既知であり、ここでの目標は、関数がいつ返されるか、その参照環境がどのように維持/参照されるかを理解することです。

さぁ、始めよう。

ECMA 262 v 5 仕様のセクション8.6.2には、ECMAScript オブジェクトの内部プロパティがリストされています。ここで指摘するのは、表 9 の [[Scope]] プロパティです。このプロパティの説明によると、次のように説明されています。

Function オブジェクトが実行される環境を定義するレキシカル環境。標準の組み込み ECMAScript オブジェクトのうち、Function オブジェクトだけが [[Scope]] を実装します。

これから見ていくように、関数オブジェクトの [[Scope]] プロパティは常に親のレキシカル環境に設定されます。これについては、関数オブジェクトを作成するプロセスについて説明しているセクション13.2で言及されています。(注: このコンテキストでの関数オブジェクトは、ネイティブ ECMAScript オブジェクトを参照しており、コードを通じてアクセス可能な関数オブジェクトではありません)。

関数が作成されると、関数が関数宣言関数式、または関数コンストラクターによって作成されたかどうかに応じて、実行中の実行コンテキストの VariableEnvironment、LexicalEnvironment、またはグローバル環境に内部 [[Scope]] プロパティを設定します。

制御がグローバル コードに渡されるとき、および制御が関数コードに入るとき、実行コンテキストの初期化の一部として宣言バインディングのインスタンス化が発生します。宣言バインディングのインスタンス化の一部は、セクション 13.2 で述べたように、関数オブジェクトを作成することによって、現在のコンテキストのスコープ内で関数宣言をバインドすることです。以下の例は、これを示しています。

例えば

  // The global execution context has already been initialized at this stage.
  // Declaration binding instantiation has occurred and the function 
  // foo is created as a new native object with a [[Scope]] property 
  // having the value of the global execution context's VariableEnvironment
  function foo() {
    // When foo is invoked, a new execution context will be created for 
    // this function scope.  When declaration binding instantiation occurs, 
    // bar will be created as a new native object with a [[Scope]] property
    // having the value of the foo execution context's VariableEnvironment
    function bar() {
      }
    bar(); // Call bar
  }
  foo();

もう 1 つの注目すべき点は、関数に入るときに実行コンテキストを入力/作成するときに発生するプロセスです。以下は、何が起こるかの要約です。

  1. NewDeclarativeEnvironment を内部的に呼び出して、新しいレキシカル環境タイプを作成します。関数の [[Scope]] プロパティは、「レキシカル環境」チェーンを維持するために外部参照として設定されます。([[Scope]] プロパティが設定され、常に親のレキシカル スコープになることを思い出してください。また、レキシカル環境チェーンは私が作成したフレーズであり、識別子まで外部参照を介してレキシカル環境をトラバースすることによって識別子を解決することを指す概念です。解決できます。)
  2. LexicalEnvironment と VariableEnvironment を、手順 1 で新しく作成した Lexical Environment に設定します。
  3. 宣言バインディングのインスタンス化を実行します。

関数が内部の [[Scope]] プロパティを介して親のレキシカル環境への参照を保持しているという知識があれば、クロージャがどのように機能するかがわかります。

<script>
// foo.[[Scope]] was set to the global environment during the global execution context initialization
  function foo() {
    var x = 1;
    // bar.[[Scope]] was set to foo's lexical environment during foo's execution context initialization
    function bar() {
      var y = 2;
      alert(x + y);
    }
    return bar;
  }

   var dummy = foo(); // Assign variable binding "dummy" to a reference of the "bar" function.
   dummy(); // Calls the "bar" function code.  The reference still has it's [[Scope]] property set, thus the identifier look-ups work as expected when resolving the identifiers.
   alert(dummy.name); // Alerts "bar";

</script>

質問に答えるために、関数の親 LexicalEnvironment は、関数の内部 [[Scope]] プロパティを通じて永続化されます。関数内のローカル変数は、関数の実行時に解決できることに注意してください。追跡する必要があるのは「フリー変数」のみであり、[[Scope]] プロパティによって実行されます。

注: 私の情報が間違っている場合は、以下にコメントしてください。

于 2013-02-27T18:33:29.570 に答える