JavaScript はオブジェクト指向言語です。 JavaScript には、「すべてはオブジェクトである」という非常に古典的な格言があります。オブジェクト指向であるため、カプセル化、継承、ポリモーフィズムというオブジェクト指向の 3 つの主要な特徴があります。ここで説明しているのは JavaScript の継承であり、他の 2 つについては後ほど説明します。
JavaScript の継承は C の継承とは異なります。C の継承はクラスに基づいていますが、JavaScript の継承はプロトタイプに基づいています。
ここで問題が起こります。
プロトタイプとは何ですか?プロトタイプについては、オブジェクトのプロパティとメソッドも保存する C のクラスを参照できます。たとえば、単純なオブジェクト
を書いてみましょう。これは、属性名とメソッド setName を持つオブジェクト Animal であることがわかります。メソッドの追加など、プロトタイプが変更されると、オブジェクトのすべてのインスタンスがこのメソッドを共有することに注意してください。たとえば
現時点では、animal には name 属性のみがあります。一文を加えるなら、
この時点で、animal にも setName メソッドが追加されます。
このコピーを継承します - 空のオブジェクトから開始します。JS の基本的な型の中にオブジェクトと呼ばれるものがあり、その最も基本的なインスタンスは空のオブジェクト、つまり new Object を直接呼び出すことによって生成されるインスタンスであることがわかっています。 ()、またはリテラル { } を使用して宣言されます。空のオブジェクトは、事前定義されたプロパティとメソッドのみを備えた「クリーン オブジェクト」であり、他のすべてのオブジェクトは空のオブジェクトを継承するため、すべてのオブジェクトがこれらの事前定義されたプロパティとメソッドを持ちます。プロトタイプは実際にはオブジェクト インスタンスです。プロトタイプの意味は、コンストラクターにプロトタイプ オブジェクト A がある場合、コンストラクターによって作成されたインスタンスは A からコピーされる必要があることです。インスタンスはオブジェクト A からコピーされるため、インスタンスは A のプロパティ、メソッド、およびその他のプロパティをすべて継承する必要があります。では、レプリケーションはどのように実現されるのでしょうか?方法 1: 構築のコピー インスタンスが構築されるたびに、インスタンスはプロトタイプからコピーされ、新しいインスタンスとプロトタイプは同じメモリ空間を占有します。これにより、obj1 と obj2 はプロトタイプと「完全に一致」しますが、メモリ空間の消費量が急速に増加するため、非常に不経済でもあります。写真に示すように:
方法 2: コピーオンライト この戦略は、システムに対する一貫したトリックであるコピーオンライトから生まれています。この種の欺瞞の典型的な例は、書き込み時に常にメモリ領域がコピーされるオペレーティング システムのダイナミック リンク ライブラリ (DDL) です。写真に示すように:
obj1 と obj2 がプロトタイプと等しいことをシステムに示す必要があるだけです。そのため、読み取るときは、プロトタイプを読み取るための指示に従うだけで済みます。オブジェクト (obj2 など) のプロパティを記述する必要がある場合は、プロトタイプ イメージをコピーし、今後の操作をこのイメージに向けます。写真に示すように:
この方法の利点は、インスタンスの作成時や属性の読み取り時に大量のメモリ オーバーヘッドが必要ないことです。初回書き込み時にメモリを割り当てるためにコードを使用するだけで、コードとメモリのオーバーヘッドが発生します。 。しかし、イメージへのアクセス効率はプロトタイプへのアクセスと同じであるため、そのようなオーバーヘッドはもうありません。ただし、書き込み操作を頻繁に実行するシステムの場合、この方法は前の方法ほど経済的ではありません。方法 3: 読み取りトラバーサル この方法では、レプリケーションの粒度がプロトタイプからメンバーに変更されます。このメソッドの特徴は、インスタンスのメンバーを書き込むときのみ、メンバー情報をインスタンスイメージにコピーすることです。たとえば (obj2.value=10) のようなオブジェクト属性を書き込むと、value という名前の属性値が生成され、obj2 オブジェクトのメンバー リストに配置されます。写真を見てください:
obj2 は依然としてプロトタイプを指す参照であり、操作中にプロトタイプと同じサイズのオブジェクト インスタンスが作成されないことがわかります。このようにして、書き込み操作によって大量のメモリ割り当てが発生しないため、メモリの使用量が節約されます。違いは、obj2 (およびすべてのオブジェクト インスタンス) がメンバー リストを維持する必要があることです。このメンバー リストは 2 つのルールに従います。 読み取り時に最初にアクセスされることが保証されます。 オブジェクトに属性が指定されていない場合は、プロトタイプが空になるか属性が見つかるまで、オブジェクトのプロトタイプ チェーン全体を走査しようとします。プロトタイプチェーンについては後で説明します。明らかに、3 つの方法の中で読み取りトラバーサルのパフォーマンスが最も優れています。したがって、JavaScript のプロトタイプの継承は読み取りトラバーサルです。コンストラクター C に慣れている人は、トップ オブジェクトのコードを読んだ後、間違いなく混乱するでしょう。結局のところ、class キーワードがない方が理解しやすいのですが、function キーワードは別のキーワードです。しかし、コンストラクターについてはどうでしょうか?実はJavaScriptにも同様のコンストラクターがありますが、これをコンストラクターと呼びます。 new 演算子を使用する場合、コンストラクターが実際に呼び出され、これがオブジェクトにバインドされます。たとえば、次のコードを使用します
動物は未定義になります。戻り値がないのは当然未定義だという人もいるでしょう。次に、Animal のオブジェクト定義を変更すると:
今何の動物だと思いますか?
このとき、動物はウィンドウになります。異なるのは、ウィンドウが拡張されて名前属性を持つことです。これは、指定されていない場合、デフォルトで最上位変数である window が使用されるためです。 new キーワードを呼び出すことによってのみ、コンストラクターを正しく呼び出すことができます。では、ユーザーが新しいキーワードを見逃さないようにするにはどうすればよいでしょうか?いくつかの小さな変更を加えることができます:
こうすることで確実に対処できます。コンストラクターは、インスタンスがどのオブジェクトに属しているかを示すためにも使用されます。 instanceofを使って判断することもできますが、instanceofは継承時に先祖オブジェクトと実オブジェクトの両方に対してtrueを返すため、適切ではありません。コンストラクターが new で呼び出される場合、デフォルトでは現在のオブジェクトを指します。
別の考え方もできます。関数が初期化されるとき、プロトタイプにはまったく値がありません。実装は次のロジックになる可能性があります。
//__proto__ を関数の組み込みメンバーとして設定し、get_prototyoe() がそのメソッドです
この利点は、関数が宣言されるたびにオブジェクト インスタンスを作成する必要がなくなり、オーバーヘッドが節約されることです。コンストラクターは変更できますが、これについては後で説明します。プロトタイプベースの継承 継承が何であるかは誰もがほぼ知っていると思うので、IQ の下限をひけらかすつもりはありません。
JS 継承にはいくつかのタイプがあります。ここでは 2 つのタイプを紹介します
1. 方法 1 この方法は最も一般的に使用されており、セキュリティが優れています。まず 2 つのオブジェクトを定義しましょう
継承の構築は非常に簡単で、子オブジェクトのプロトタイプを親オブジェクトのインスタンスにポイントします (これはオブジェクトではなくインスタンスであることに注意してください)
この時点で、犬には名前と年齢という 2 つの属性が与えられます。そして、犬にinstanceof演算子
を使用すると、このようにして継承が行われますが、小さな問題があります
コンストラクターが指すオブジェクトが変更されていることがわかりますが、これでは新しいインスタンスが誰に属するかを判断できません。したがって、次の文を追加できます:
もう一度見てみましょう:
完了。このメソッドはプロトタイプ チェーンのメンテナンスの一部であり、以下で詳しく説明します。 2. 方法 2 この方法には長所と短所がありますが、短所が長所を上回ります。まずはコードを見てみましょう
function Animal(name) {
This.name = 名前;
}
Animal.prototype.setName = 関数(名前) {
This.name = 名前;
}
関数 犬(年齢) {
This.age = 年齢;
}
Dog.prototype = Animal.prototype;
これによりプロトタイプのコピーが実現されます。
この方法の利点は、(方法 1 と比較して) オブジェクトをインスタンス化する必要がないため、リソースが節約されることです。上記と同じ問題に加えて、コンストラクターは親オブジェクトを指し、親オブジェクトのプロトタイプによって宣言されたプロパティとメソッドしかコピーできないという欠点も明らかです。つまり、上記のコードでは、Animal オブジェクトの name 属性はコピーできませんが、setName メソッドはコピーできます。最も致命的なのは、子オブジェクトのプロトタイプを変更すると、親オブジェクトのプロトタイプに影響が及ぶことです。つまり、両方のオブジェクトによって宣言されたインスタンスが影響を受けることになります。したがって、この方法はお勧めできません。
プロトタイプチェーン
継承を書いたことがある人なら誰でも、継承は複数のレベルで継承できることを知っています。 JS では、これはプロトタイプ チェーンを構成します。プロトタイプチェーンについては上で何度も言及しましたが、プロトタイプチェーンとは何でしょうか?インスタンスには少なくとも、JavaScript のオブジェクト システムの基礎であるプロトタイプを指す proto 属性が必要です。ただし、このプロパティは目に見えません。コンストラクターのプロトタイプで構成される「コンストラクター プロトタイプ チェーン」(通常、「プロトタイプ チェーン」と呼ばれます) と区別するために、これを「内部プロトタイプ チェーン」と呼びます。まず、上記のコードに従って単純な継承関係を構築しましょう:
前述したように、すべてのオブジェクトは空のオブジェクトを継承します。したがって、プロトタイプチェーンを構築しました:
子オブジェクトのプロトタイプが親オブジェクトのインスタンスを指しており、コンストラクター プロトタイプ チェーンを形成していることがわかります。子インスタンスの内部プロト オブジェクトも親オブジェクトのインスタンスを指し、内部プロトタイプ チェーンを形成します。特定の属性を見つける必要がある場合、コードは
に似ています。この例では、dog の name 属性を検索すると、dog のメンバー リストで検索されます。これは、dog のメンバー リストには年齢しか含まれていないため、見つかりません。次に、プロトタイプ チェーン、つまり .proto によって指定されたインスタンスに沿って検索を続けます。つまり、animal では、name 属性を見つけて返します。存在しないプロパティを探していて、animal で見つからない場合は、.proto に沿って検索を続け、空のオブジェクトを見つけます。それが見つからない場合は、.proto に沿って検索を続けます。空のオブジェクトは null を指しており、出口を探しています。
プロトタイプ チェーンのメンテナンス プロトタイプの継承について説明したときに、メソッド 1 を使用して継承を構築する場合、子オブジェクト インスタンスのコンストラクターは親オブジェクトを指します。この利点は、コンストラクター属性を通じてプロトタイプ チェーンにアクセスできることですが、欠点も明らかです。オブジェクト、それが生成するインスタンスはそれ自体を指す必要があります、つまり
しかし、さらに 2 つの問題があります:
__proto__ はオーバーライド可能です。つまり、使用時に依然としてリスクが存在します
__proto__ は SpiderMonkey の特殊な処理であり、他のエンジン (JScript など) では使用できません。
別の方法もあります。それは、プロトタイプのコンストラクター プロパティを保持し、サブクラスのコンストラクター関数内でインスタンスのコンストラクター プロパティを初期化することです。
コードは次のとおりです: サブオブジェクトを書き換えます