ホームページ > ウェブフロントエンド > jsチュートリアル > プロトタイプの継承の詳細な説明は JavaScript_javascript のヒントで説明されています。

プロトタイプの継承の詳細な説明は JavaScript_javascript のヒントで説明されています。

WBOY
リリース: 2016-05-16 16:14:05
オリジナル
1113 人が閲覧しました

JavaScript はオブジェクト指向言語です。 JavaScript には、「すべてはオブジェクトである」という非常に古典的な格言があります。オブジェクト指向であるため、カプセル化、継承、ポリモーフィズムというオブジェクト指向の 3 つの主要な特徴があります。ここで説明しているのは JavaScript の継承であり、他の 2 つについては後ほど説明します。

JavaScript の継承は C の継承とは異なります。C の継承はクラスに基づいていますが、JavaScript の継承はプロトタイプに基づいています。

ここで問題が起こります。

プロトタイプとは何ですか?プロトタイプについては、オブジェクトのプロパティとメソッドも保存する C のクラスを参照できます。たとえば、単純なオブジェクト

を書いてみましょう。

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
This.name = 名前;
}
Animal.prototype.setName = 関数(名前) {
This.name = 名前;
}
var Animal = new Animal("wangwang");

これは、属性名とメソッド setName を持つオブジェクト Animal であることがわかります。メソッドの追加など、プロトタイプが変更されると、オブジェクトのすべてのインスタンスがこのメソッドを共有することに注意してください。たとえば

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
This.name = 名前;
}
var Animal = new Animal("wangwang");

現時点では、animal には name 属性のみがあります。一文を加えるなら、

コードをコピーします コードは次のとおりです:

Animal.prototype.setName = 関数(名前) {
This.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 演算子を使用する場合、コンストラクターが実際に呼び出され、これがオブジェクトにバインドされます。たとえば、次のコードを使用します

コードをコピーします コードは次のとおりです:

var Animal = Animal("wangwang");

動物は未定義になります。戻り値がないのは当然未定義だという人もいるでしょう。次に、Animal のオブジェクト定義を変更すると:

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
This.name = 名前;
これを返してください;
}

今何の動物だと思いますか?
このとき、動物はウィンドウになります。異なるのは、ウィンドウが拡張されて名前属性を持つことです。これは、指定されていない場合、デフォルトで最上位変数である window が使用されるためです。 new キーワードを呼び出すことによってのみ、コンストラクターを正しく呼び出すことができます。では、ユーザーが新しいキーワードを見逃さないようにするにはどうすればよいでしょうか?いくつかの小さな変更を加えることができます:

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
If(!(Animal のこのインスタンス)) {
新しい動物(名前)を返します
}
This.name = 名前;
}

こうすることで確実に対処できます。コンストラクターは、インスタンスがどのオブジェクトに属しているかを示すためにも使用されます。 instanceofを使って判断することもできますが、instanceofは継承時に先祖オブジェクトと実オブジェクトの両方に対してtrueを返すため、適切ではありません。コンストラクターが new で呼び出される場合、デフォルトでは現在のオブジェクトを指します。

コードをコピーします コードは次のとおりです:

console.log(Animal.prototype.constructor === Animal); // true

別の考え方もできます。関数が初期化されるとき、プロトタイプにはまったく値がありません。実装は次のロジックになる可能性があります。

//__proto__ を関数の組み込みメンバーとして設定し、get_prototyoe() がそのメソッドです

コードをコピーします コードは次のとおりです:

var __proto__ = null;
関数 get_prototype() {
If(!__proto__) {
__proto__ = 新しいオブジェクト();
__proto__.constructor = this;
}
__proto__ を返します;
}

この利点は、関数が宣言されるたびにオブジェクト インスタンスを作成する必要がなくなり、オーバーヘッドが節約されることです。コンストラクターは変更できますが、これについては後で説明します。プロトタイプベースの継承 継承が何であるかは誰もがほぼ知っていると思うので、IQ の下限をひけらかすつもりはありません。

JS 継承にはいくつかのタイプがあります。ここでは 2 つのタイプを紹介します

1. 方法 1 この方法は最も一般的に使用されており、セキュリティが優れています。まず 2 つのオブジェクトを定義しましょう

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
This.name = 名前;
}
関数 犬(年齢) {
This.age = 年齢;
}
var Dog = 新しい Dog(2);

継承の構築は非常に簡単で、子オブジェクトのプロトタイプを親オブジェクトのインスタンスにポイントします (これはオブジェクトではなくインスタンスであることに注意してください)

コードをコピーします コードは次のとおりです:

Dog.prototype = new Animal("wangwang");

この時点で、犬には名前と年齢という 2 つの属性が与えられます。そして、犬にinstanceof演算子

を使用すると、

コードをコピーします コードは次のとおりです:

console.log(動物の犬のインスタンス) // true
; console.log(犬のインスタンス) // false

このようにして継承が行われますが、小さな問題があります

コードをコピーします コードは次のとおりです:

console.log(Dog.prototype.constructor === Animal) // true
; console.log(Dog.prototype.constructor === 犬) // false

コンストラクターが指すオブジェクトが変更されていることがわかりますが、これでは新しいインスタンスが誰に属するかを判断できません。したがって、次の文を追加できます:

コードをコピーします コードは次のとおりです:

Dog.prototype.constructor = 犬;

もう一度見てみましょう:

コードをコピーします コードは次のとおりです:

console.log(動物の犬のインスタンス) // false
; console.log(犬のインスタンス) // true
;

完了。このメソッドはプロトタイプ チェーンのメンテナンスの一部であり、以下で詳しく説明します。 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 属性が必要です。ただし、このプロパティは目に見えません。コンストラクターのプロトタイプで構成される「コンストラクター プロトタイプ チェーン」(通常、「プロトタイプ チェーン」と呼ばれます) と区別するために、これを「内部プロトタイプ チェーン」と呼びます。まず、上記のコードに従って単純な継承関係を構築しましょう:

コードをコピーします コードは次のとおりです:

関数 動物(名前) {
This.name = 名前;
}
関数 犬(年齢) {
This.age = 年齢;
}
var Animal = new Animal("wangwang");
Dog.prototype = 動物;
var Dog = 新しい Dog(2);

前述したように、すべてのオブジェクトは空のオブジェクトを継承します。したがって、プロトタイプチェーンを構築しました:


子オブジェクトのプロトタイプが親オブジェクトのインスタンスを指しており、コンストラクター プロトタイプ チェーンを形成していることがわかります。子インスタンスの内部プロト オブジェクトも親オブジェクトのインスタンスを指し、内部プロトタイプ チェーンを形成します。特定の属性を見つける必要がある場合、コードは

に似ています。

コードをコピーします コードは次のとおりです:

function getAttrFromObj(attr, obj) {
If(typeof(obj) === "オブジェクト") {
var proto = obj;
while(プロト) {
If(proto.hasOwnProperty(attr)) {
return proto[attr];
}
proto = proto.__proto__;
}
}
未定義を返します;
}

この例では、dog の name 属性を検索すると、dog のメンバー リストで検索されます。これは、dog のメンバー リストには年齢しか含まれていないため、見つかりません。次に、プロトタイプ チェーン、つまり .proto によって指定されたインスタンスに沿って検索を続けます。つまり、animal では、name 属性を見つけて返します。存在しないプロパティを探していて、animal で見つからない場合は、.proto に沿って検索を続け、空のオブジェクトを見つけます。それが見つからない場合は、.proto に沿って検索を続けます。空のオブジェクトは null を指しており、出口を探しています。

プロトタイプ チェーンのメンテナンス プロトタイプの継承について説明したときに、メソッド 1 を使用して継承を構築する場合、子オブジェクト インスタンスのコンストラクターは親オブジェクトを指します。この利点は、コンストラクター属性を通じてプロトタイプ チェーンにアクセスできることですが、欠点も明らかです。オブジェクト、それが生成するインスタンスはそれ自体を指す必要があります、つまり

コードをコピーします コードは次のとおりです:

(new obj()).prototype.constructor === obj;
その後、プロトタイプ プロパティをオーバーライドすると、サブオブジェクトによって生成されたインスタンスのコンストラクターはそれ自体を指しません。これは構築者の本来の意図に反します。解決策については上で説明しました:

コードをコピーします コードは次のとおりです:
Dog.prototype = new Animal("wangwang");
Dog.prototype.constructor = 犬;

問題ないようです。しかし実際には、これによって新たな問題が発生します。親オブジェクトが見つからず、内部プロトタイプ チェーンの .proto プロパティにアクセスできないため、プロトタイプ チェーンを後戻りできないことがわかります。したがって、SpiderMonkey は改善されたソリューションを提供します。つまり、作成されたオブジェクトに __proto__ という名前の属性を追加します。この属性は、コンストラクターによって使用されるプロトタイプを常に指します。この方法では、コンストラクターを変更しても __proto__ の値に影響を与えないため、コンストラクターの保守が容易になります。

しかし、さらに 2 つの問題があります:

__proto__ はオーバーライド可能です。つまり、使用時に依然としてリスクが存在します

__proto__ は SpiderMonkey の特殊な処理であり、他のエンジン (JScript など) では使用できません。

別の方法もあります。それは、プロトタイプのコンストラクター プロパティを保持し、サブクラスのコンストラクター関数内でインスタンスのコンストラクター プロパティを初期化することです。

コードは次のとおりです: サブオブジェクトを書き換えます

コードをコピーします コードは次のとおりです:
関数 犬(年齢) {
This.constructor = argument.callee;
This.age = 年齢;
}
Dog.prototype = new Animal("wangwang");

このようにして、すべての子オブジェクト インスタンスのコンストラクターはオブジェクトを正しく指し、プロトタイプのコンストラクターは親オブジェクトを指します。この方法は、インスタンスを構築するたびにコンストラクタ属性を書き換える必要があるため、比較的非効率ではありますが、前述の矛盾を効果的に解決できることは間違いありません。 ES5 はこの状況を考慮し、この問題を完全に解決します。コンストラクターにアクセスしたり、外部プロトタイプ チェーンを維持したりすることなく、いつでも Object.getPrototypeOf() を使用してオブジェクトの実際のプロトタイプを取得できます。したがって、前のセクションで説明したようにオブジェクトのプロパティを見つけるには、次のように書き換えることができます:

コードをコピーします コードは次のとおりです:
function getAttrFromObj(attr, obj) {
If(typeof(obj) === "オブジェクト") {
{
を実行します var proto = Object.getPrototypeOf(dog);
If(proto[attr]) {
return proto[attr];
}
}
while(プロト);
}
未定義を返します;
}

もちろん、この方法は ES5 をサポートするブラウザでのみ使用できます。下位互換性のために、以前の方法を考慮する必要があります。より適切な方法は、これら 2 つの方法を統合してカプセル化することです。読者の皆さんはこれが得意だと思いますので、ここでは紹介しません。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート