適切な読者: 経験豊富な開発者、プロのフロントエンド担当者。
原著者: Dmitry A. Soshnikov
公開時期: 2010-09-02
原文: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
参考文献1 : http://ued.ctrip.com/blog/?p=2795
参考 2: http://www.cnblogs.com/ifishing/archive/2010/12/08/1900594.html
Main 上記 2 人の専門家の中国語訳を組み合わせ、2 つの記事の良い部分を組み合わせたものです。
まず、ECMASript の最も基本的な概念でもあるオブジェクト [Object] の概念を見てみましょう。
Object オブジェクト
ECMAScript は高度に抽象化されたオブジェクト指向言語であり、Object オブジェクトを処理するために使用されます。もちろん、基本的な型もありますが、必要に応じてオブジェクト オブジェクトに変換する必要もあります。 。 使用。
オブジェクトはプロパティのコレクションであり、プロトタイプ オブジェクトはオブジェクトまたは null 値のいずれかです。このプロトタイプ オブジェクト [プロトタイプ オブジェクト] は、オブジェクトまたは null 値。
コードをコピー
基本的なオブジェクトの例を見てみましょう。まず、オブジェクトのプロトタイプが内部 [[prototype]] 属性への参照であることを理解する必要があります。
しかし、一般的に言えば、__proto__ のような二重括弧の代わりに __<内部属性名>__ アンダースコアを使用します (これは、SpiderMonkey などの一部のスクリプト エンジンのプロトタイプ概念の特定の実装ですが、標準ではありません) )。
図 1. プロトタイプを備えた基本オブジェクト
なぜプロトタイプが必要なのでしょうか? この質問に答えるために、プロトタイプ チェーンの概念を考えてみましょう。
プロトタイプ チェーン
プロトタイプ オブジェクトも通常のオブジェクトであり、プロトタイプ オブジェクトのプロトタイプが null でない場合、それをプロトタイプ チェーンと呼びます。 )。
プロトタイプ チェーンは、継承と共有プロパティの実装に使用されるオブジェクトの有限チェーンです。
次のような状況を想像してください。2 つのオブジェクトがあり、コンテンツの大部分は同じで、ほんの一部だけが異なります。明らかに、適切なデザイン パターンでは、同じ部分を再利用する必要があります。すべてのオブジェクトで同じメソッドやプロパティを繰り返し定義しないでください。クラスベースのシステムでは、これらの再利用された部分はクラスの継承と呼ばれます。同じ部分がクラス A に置かれ、次にクラス B とクラス C が A から継承し、それぞれに固有のものを要求できます。
ECMAScript にはクラスの概念がありません。ただし、再利用の概念は同じであり (ある点ではクラスよりも柔軟です)、プロトタイプ チェーンによって実装できます。この種の継承は、委任ベースの継承、またはより一般的にはプロトタイプ継承と呼ばれます。
クラス "A"、"B"、"C" と同様に、ECMAScript ではオブジェクト クラス "a"、"b"、"c" を作成します。したがって、オブジェクト "a" はオブジェクト " を所有します。 b」と「c」の一部。同時に、オブジェクト "b" と "c" には、独自の追加プロパティまたはメソッドのみが含まれます。
図 2. プロトタイプ チェーン
プロトタイプ チェーンは通常、オブジェクトが同じまたは類似の状態構造 (つまり、同じプロパティのセット) と異なる状態値を持つ状況で使用されます。この場合、Constructor を使用して、指定されたパターンでオブジェクトを作成できます。
コンストラクター
コンストラクターは、オブジェクトの作成に加えて、作成された新しいオブジェクトのプロトタイプ オブジェクトを自動的に設定するという別の便利な機能も実行します。プロトタイプ オブジェクトは ConstructorFunction.prototype プロパティに保存されます。
たとえば、前の例を書き直し、コンストラクターを使用してオブジェクト "b" と "c" を作成すると、オブジェクト "a" は "Foo.prototype" の役割を果たします。
図 3. コンストラクターとオブジェクトの関係
上の図からわかるように、各オブジェクトにはプロトタイプがあり、コンストラクター Foo には独自の __proto__ (Function.prototype) があり、Function.prototype の __proto__ は Object.prototype を指します。 Foo.prototype は単なる明示的な属性、つまり b と c の __proto__ 属性です。
この問題の完全かつ詳細な説明は、叔父が間もなく翻訳する第 18 章と第 19 章に記載されています。オブジェクト指向プログラミング (OOP。一般理論) では、さまざまなオブジェクト指向のパラダイムとスタイル (OOP のパラダイムとスタイル) と、ECMAScript の実装との比較を説明します。 OOP) 。ECMAScript 実装)、特に ECMAScript でのオブジェクト指向プログラミングについて説明します。
オブジェクトの基本原理を理解したところで、ECMAScript のプログラム実行環境 [ランタイム プログラム実行] を見てみましょう。これは一般に「実行コンテキスト スタック」[実行コンテキスト] スタックと呼ばれます。各要素はオブジェクトとして抽象的に理解できます。 ECMAScript では、ほとんどどこにでもオブジェクトが表示されることに気づいたかもしれません。
実行コンテキスト スタック
ECMASscript には、global、function、eval の 3 種類のコードがあります。
各タイプのコードの実行は、それぞれのコンテキストに依存します。もちろん、グローバル コンテキストは多くの関数および評価インスタンスをカバーする可能性があります。関数が呼び出されるたびに、関数実行のコンテキストに入り、関数内の変数の値が計算されます。 eval 関数を実行するたびに、eval 実行のコンテキストにも入り、変数の値をどこで取得するかを決定します。
関数呼び出し (再帰でも) は新しいコンテキストを生成するため、関数は無制限のコンテキストを生成する可能性があることに注意してください。
図 4. 実行コンテキスト スタック
プログラムが開始されると、まずグローバル実行コンテキスト [グローバル実行コンテキスト] に入ります。これはスタックの最下位要素でもあります。このグローバル プログラムは、このグローバル コンテキストの実行中に、必要なオブジェクトと関数の初期化と生成を開始し、いくつかのメソッド (もちろんすでに初期化されています) をアクティブ化し、そのコンテキストに新しい要素をプッシュします。これらの初期化が完了すると、システムはいくつかのイベント (ユーザーのマウス クリックなど) を待機し、いくつかのメソッドをトリガーして、新しいコンテキストに入ります。
図 5 を参照してください。関数コンテキスト「EC1」とグローバル コンテキスト「Global EC」があります。次の図は、「Global EC」から「EC1」に入るときと出るときのスタックの変化を示しています。
図 5. 実行コンテキスト スタックの変更
これは、ECMAScript ランタイム システムがコードの実行を管理する方法です。
ECMAScript 実行コンテキスト スタックの詳細については、この一連のチュートリアルの
第 11 章 実行コンテキスト (実行コンテキスト) を参照してください。
上で述べたように、スタック内の各実行コンテキストはオブジェクトとして表すことができます。コンテキスト オブジェクトの構造と、そのコードを実行するために必要な状態を見てみましょう。
実行コンテキスト
実行コンテキストは、オブジェクトとして抽象的に理解できます。各実行コンテキストには、関連するコードの実行の進行状況を追跡するために使用される一連のプロパティ (コンテキスト状態と呼ばれます) があります。この図はコンテキストの構造です。
図 6. コンテキスト構造
これら 3 つの必須属性 (変数オブジェクト (変数オブジェクト)、このポインター (この値)、スコープ チェーン (スコープ チェーン)) に加えて、実行コンテキストは特定の実装には追加のプロパティを持つこともできます。次に、これら 3 つのプロパティを詳しく見てみましょう。
変数オブジェクト
変数オブジェクトは、実行コンテキストに関連するデータのスコープです。
コンテキスト内で定義されている変数と関数宣言を格納する、コンテキストに関連付けられた特別なオブジェクトです。
変数オブジェクトは、実行コンテキストに関連するデータのスコープです。
これはコンテキストに関連付けられた特別なオブジェクトであり、コンテキストで定義された変数と関数宣言を保存するために使用されます。
コードをコピー
注: 関数式 [関数式] (関数宣言ではなく、違いについてはこのシリーズの第 2 章を参照してください) は、VO[変数オブジェクト] には含まれません。
変数オブジェクトは、さまざまなコンテキストでのさまざまなオブジェクトの使用を表す抽象的な概念です。たとえば、グローバル コンテキストでは、変数オブジェクトはグローバル オブジェクトそのものでもあります。 (これが、グローバル オブジェクトのプロパティを通じてグローバル変数を指すことができる理由です)。
次の例でグローバル実行コンテキストを見てみましょう:
図 7. グローバル変数オブジェクト
上に示したように、関数「baz」を関数式として使用した場合、変数オブジェクトには含まれません。これが、関数の外でアクセスしようとすると ReferenceError が発生する理由です。 ECMAScript では、他の言語 (C/C++ など) と比較して、関数のみが新しいスコープを作成できることに注意してください。関数内で定義された変数と内部関数は外部からは直接見えず、グローバル オブジェクトを汚染しません。 eval を使用する場合は、新しい (eval で作成された) 実行コンテキストも使用します。 eval は、グローバル変数オブジェクトまたは呼び出し元の変数オブジェクト (eval の呼び出しのソース) を使用します。
関数とその独自の変数オブジェクトはどうなるでしょうか? 関数のコンテキストでは、変数オブジェクトはアクティベーション オブジェクトとして表されます。
アクティブ化オブジェクト
呼び出し元によって関数がアクティブ化されると、この特別なアクティブ化オブジェクトが作成されます。これには、通常のパラメータ (仮パラメータ) と特殊パラメータ (引数) オブジェクト (インデックス付き属性を持つパラメータ マッピング テーブル) が含まれます。アクティブ オブジェクトは、関数コンテキストで変数オブジェクトとして使用されます。
つまり、関数の変数オブジェクトは変更されませんが、格納変数と関数宣言に加えて、特別なオブジェクト引数も含まれます。
次の状況を考えてみましょう:
図 8. オブジェクトのアクティブ化
同様の理由で、AOには関数式は含まれていません。
この AO の詳細については、このチュートリアル シリーズの第 9 章を参照してください。
次にお話しするのは、3 番目の主要なオブジェクトです。ご存知のとおり、ECMAScript では内部関数 [内部関数] を使用し、その親関数変数またはグローバル変数を参照することがあります。これらの変数オブジェクトをコンテキストのスコープ オブジェクトと呼びます。前述のプロトタイプ チェーンと同様に、ここではスコープ チェーンと呼びます。
スコープ チェーン
スコープ チェーンは、コンテキストのコード内に出現する識別子を検索するオブジェクトのリストです。
スコープ チェーンは、オブジェクトのリスト (オブジェクトのリスト) です。 ) を使用して、コンテキスト コードに表示される識別子を取得します。
コードをコピー
スコープ チェーンの原理は、変数が独自のスコープに存在しない場合、最上位レベルまで親を探します。
識別子 [識別子] は、変数名、関数宣言、および通常のパラメーターとして理解できます。たとえば、関数が独自の関数本体内で変数を参照する必要があるが、その変数が関数内で宣言されていない (またはパラメーター名ではない) 場合、その変数は自由変数 [自由変数] と呼ばれます。次に、スコープ チェーンを使用してこれらの自由変数を検索する必要があります。
一般に、スコープ チェーンには、親変数オブジェクト (スコープ チェーンの先頭)、関数自身の変数 VO、およびアクティベーション オブジェクトが含まれます。ただし、場合によっては、with ステートメントや catch ステートメントなど、実行中にスコープ チェーンに動的に追加されるなど、他のオブジェクトも含まれることがあります。 [注釈: with-objects は、with ステートメントによって生成される一時スコープ オブジェクトを指します。catch-clauses は、例外オブジェクトを生成し、スコープの変更を引き起こす catch(e) などの catch 句を指します。
識別子を検索するときは、スコープ チェーンのアクティブ オブジェクト部分から開始し、次に (アクティブ オブジェクトで識別子が見つからない場合) スコープ チェーンの先頭から検索します。アクションと同じように、ドメインチェーンのように。
図 9. スコープ チェーン
コードの実行中に with または catch ステートメントを使用すると、スコープ チェーンが変更されます。これらのオブジェクトは単純なオブジェクトであり、プロトタイプ チェーンも持ちます。この場合、スコープ チェーンは 2 次元で検索されます。
次の例を見てみましょう:
図 10. 増加したスコープ チェーン内
すべてのグローバル オブジェクトが Object.prototype から継承されるわけではないことに注意してください。上に示した状況は SpiderMonkey でテストできます。
すべての外部関数の変数オブジェクトが存在する限り、内部関数から外部データを参照することに関して特別なことは何もありません。スコープ リストを走査して必要な変数を見つけるだけです。ただし、上で述べたように、コンテキストが終了すると、その状態とそれ自体が破棄され、内部関数が外部関数から戻ります。さらに、返された関数は後で他のコンテキストでアクティブ化される可能性があるため、いくつかの自由変数を持つ以前に終了したコンテキストが再びアクティブ化された場合はどうなるでしょうか? 一般的に、この問題を解決する概念は ECMAScript の概念と直接関係しています。および (字句) クロージャと呼ばれます。
クロージャ
ECMAScript では、関数は「ファーストクラス」オブジェクトです。この用語は、関数を他の関数に引数として渡すことができることを意味します (この場合、関数は「funargs」と呼ばれます。これは「関数引数」の略です [注釈: 関数引数と適切に翻訳されているかどうかはわかりません]) 。 「funargs」を受け入れる関数は、高階関数、またはより数学的には演算子と呼ばれます。他の関数のランタイムも関数を返します。これらの返される関数は、関数値関数 (関数値を持つ関数) と呼ばれます。
「funargs」と「関数値」の間には 2 つの概念的な問題があり、これら 2 つの部分問題は「Funarg 問題」(「関数パラメータ問題」) と呼ばれます。関数パラメータの問題を正確に解決するには、クロージャの概念を導入する必要があります。これら 2 つの問題について詳しく説明します (ご覧のとおり、この問題を解決するために ECMAScript では関数の [[Scope]] 属性が使用されています)。
「funarg 問題」の副次問題は、「上向き funarg 問題」[注釈: 上向き探索関数パラメータ問題と訳される場合もあります] です。この問題は、ある関数が他の関数から外部に戻るときに発生します。外部コンテキストの終了時に外部コンテキストの変数にアクセスできるようにするには、内部関数が作成時(作成時)に [[Scope]] 属性の親要素のスコープに変数を格納する必要があります。次に、関数がアクティブ化されると、コンテキストのスコープ チェーンが [[Scope]] 属性と結合されたアクティブ化オブジェクトとして表示されます (実際、上の図で確認できます):
スコープ チェーン = アクティブ化オブジェクト[[ Scope]]
スコープ チェーン = アクティブ オブジェクト [[Scope]]
重要なことは、この保存されたロールにより、関数は作成時に外側のスコープを保存することに注意してください。スコープ チェーンは、将来の関数呼び出しでの変数検索に使用されます。
図 11. 共有 [[スコープ]]
上の図は、ループ内に複数の関数を作成するときに混乱を引き起こす可能性があります。作成した関数でループ変数 (「k」など) を使用すると、すべての関数で同じループ変数が使用されるため、一部のプログラマが期待した値を取得できないことがよくあります。なぜこのような問題が発生するのかは明らかです。すべての関数が同じ [[Scope]] を共有しており、ループ変数が最後の複雑な代入であるためです。