JavaScriptはオブジェクト指向の言語です。 JavaScriptには非常に古典的なことわざがあります。すべてがオブジェクトです。オブジェクト指向であるため、オブジェクト指向の3つの主要な特性があります。カプセル化、継承、および多型です。ここでは、JavaScriptの相続について説明します。他の2つについては、後で説明します。
JavaScriptの継承は、C ++の継承とはあまり同じではありません。 C ++の継承はクラスに基づいていますが、JavaScriptの継承はプロトタイプに基づいています。
今、問題はここにあります。
プロトタイプとは何ですか?プロトタイプの場合、C ++のクラスを参照し、オブジェクトのプロパティとメソッドを保存できます。たとえば、シンプルなオブジェクトを書きましょう
コードコピーは次のとおりです。
function animal(name){
this.name = name;
}
animal.prototype.setname = function(name){
this.name = name;
}
var Animal = new Animal( "Wangwang");
これは、属性名とメソッドSetNameを備えたオブジェクト動物であることがわかります。メソッドの追加など、プロトタイプが変更されると、オブジェクトのすべてのインスタンスがこのメソッドを共有することに注意してください。例えば
コードコピーは次のとおりです。
function animal(name){
this.name = name;
}
var Animal = new Animal( "Wangwang");
現時点では、動物には名前属性のみがあります。文を追加すると、
コードコピーは次のとおりです。
animal.prototype.setname = function(name){
this.name = name;
}
現時点では、動物にはSetNameメソッドもあります。
継承コピー - 空のオブジェクトから始めて、jsの基本的なタイプの中には、オブジェクトと呼ばれるタイプがあり、その最も基本的なインスタンスは空のオブジェクト、つまり、newオブジェクト()を直接呼び出すか、リテラル{}で宣言されたインスタンスであることを知っています。空のオブジェクトは、定義されたプロパティとメソッドのみを備えた「クリーンオブジェクト」であり、他のすべてのオブジェクトは空のオブジェクトから継承されているため、すべてのオブジェクトにはこれらの事前定義されたプロパティとメソッドがあります。プロトタイプは実際にはオブジェクトインスタンスです。プロトタイプの意味は次のとおりです。コンストラクターにプロトタイプオブジェクトAがある場合、コンストラクターによって作成されたインスタンスはAからコピーする必要があります。インスタンスはオブジェクトAからコピーされるため、インスタンスはAのすべてのプロパティ、方法、およびその他のプロパティを継承する必要があります。方法1:構築された各インスタンスをコピーするのは、プロトタイプからコピーされ、新しいインスタンスはプロトタイプと同じメモリ空間を占めています。これにより、OBJ1とOBJ2はプロトタイプと「完全に一貫して」なりますが、非常に非経済的でもあります。メモリスペースの消費は急速に増加します。写真に示されているように:
方法2:書き込みのコピーこの戦略は、一貫した欺ceptionシステムのテクノロジーからのものです。書き込みにコピーします。この種の詐欺の典型的な例は、オペレーティングシステムのダイナミックリンクライブラリ(DDL)です。そのメモリ領域は常に書き込みでコピーされています。写真に示されているように:
OBJ1とOBJ2がプロトタイプと同等であることをシステムで指定する必要があるため、読むときは、プロトタイプを読むための指示に従うだけです。オブジェクト(OBJ2など)のプロパティを書き込む必要がある場合、プロトタイプ画像をコピーして、その後の操作を画像に向けます。写真に示されているように:
この方法の利点は、インスタンスを作成して属性を読み取るときに、多くのメモリオーバーヘッドを必要としないことです。初めて書くときにメモリを割り当てるためにコードのみを使用し、コードとメモリのオーバーヘッドを持ち込みます。しかし、それ以来、そのようなオーバーヘッドはありません。なぜなら、画像へのアクセスとプロトタイプへのアクセスの効率は一貫しているからです。ただし、操作を頻繁に記述するシステムの場合、この方法は以前の方法よりも経済的ではありません。方法3:トラバーサルの読み取りこの方法は、コピーの粒度をプロトタイプからメンバーに変えます。この方法は、インスタンスのメンバーを書くときにのみ、メンバー情報をインスタンス画像にコピーすることによって特徴付けられます。たとえば、オブジェクトのプロパティを作成する場合(obj2.value = 10)、名前の属性値が生成され、obj2オブジェクトのメンバーリストに配置されます。写真を見てください:
OBJ2は依然としてプロトタイプへの参照であり、操作中にプロトタイプと同じサイズのオブジェクトインスタンスが作成されていないことがわかります。このようにして、書き込み操作は大量のメモリ割り当てにつながることはないため、メモリの使用量は経済的であると思われます。違いは、OBJ2(およびすべてのオブジェクトインスタンス)がメンバーリストを維持する必要があることです。このメンバーリストは、2つのルールに従います。最初に読むときにアクセスすることが保証されています。オブジェクトに属性が指定されていない場合は、プロトタイプが空またはプロパティが見つかるまで、オブジェクトのプロトタイプチェーン全体を通過してみてください。プロトタイプチェーンについては後で説明します。明らかに、3つの方法のうち、読み取りトラバーサルが最高のパフォーマンスです。したがって、JavaScriptのプロトタイプ継承は読み取りトラバーサルです。 C ++に精通しているコンストラクターの人々は、上部オブジェクトのコードを読んだ後、間違いなく混乱します。クラスのキーワードがなければ理解するのは簡単ですが、結局のところ、機能キーワードがありますが、キーワードは異なります。しかし、コンストラクターはどうですか?実際、JavaScriptには類似したコンストラクターもありますが、それらはコンストラクターと呼ばれます。新しいオペレーターを使用する場合、コンストラクターが呼び出され、これはオブジェクトとしてバインドされています。たとえば、次のコードを使用します
コードコピーは次のとおりです。
var animal = animal( "wangwang");
動物は未定義になります。一部の人々は、もちろん定義されていないと言う人もいます。次に、動物オブジェクトの定義を変更した場合:
コードコピーは次のとおりです。
function animal(name){
this.name = name;
これを返します。
}
今どんな動物がいると思いますか?
この時点で、動物は窓になりました。違いは、ウィンドウが展開されているため、ウィンドウに名前属性があることです。これは、これがウィンドウ、つまり指定せずにトップレベルの変数になるためです。新しいキーワードを呼び出すことによってのみ、コンストラクターを正しく呼び出すことができます。それでは、新しいキーワードがそれを使用している人が見逃していることを避ける方法は?いくつかの小さな変更を加えることができます:
コードコピーは次のとおりです。
function animal(name){
if(!(このinstanceof animal)){
新しい動物(名前)を返します。
}
this.name = name;
}
これは絶対確実になります。コンストラクターには別の用途もあり、インスタンスがどのオブジェクトに属しているかを示します。 InstanceOfを使用して判断することはできますが、InstanceOFは継承するときに先祖オブジェクトと実際のオブジェクトにtrueを返しますので、それはあまり適していません。コンストラクターが新品と呼ばれると、デフォルトで現在のオブジェクトを指します。
コードコピーは次のとおりです。
console.log(animal.prototype.constructor === Animal); // 真実
私たちは違った方法で考えることができます:プロトタイプは関数の開始時に価値がなく、実装は次のロジックかもしれません
// set __proto__は関数の組み込みメンバーであり、get_prototyoe()はその方法です
コードコピーは次のとおりです。
var __proto__ = null;
function get_prototype(){
if(!__ proto__){
__proto__ = new object();
__proto __。constructor = this;
}
return __proto__;
}
この利点は、宣言された機能ごとにオブジェクトインスタンスを作成し、オーバーヘッドを保存することを避けることです。コンストラクターは変更できます。これについては後で説明します。誰もがプロトタイプに基づいている継承を知っているので、IQの下限を誇示しないと思います。
JS継承にはいくつかのタイプがありますが、ここに2つのタイプがあります
1.方法1この方法は最も一般的に使用されており、安全性が向上しています。最初に2つのオブジェクトを定義しましょう
コードコピーは次のとおりです。
function animal(name){
this.name = name;
}
function dog(age){
this.age = age;
}
var dog = new Dog(2);
継承を構築するのは非常に簡単です。子オブジェクトのプロトタイプを親オブジェクトのインスタンスに向けます(オブジェクトではなくインスタンスであることに注意してください)
コードコピーは次のとおりです。
dog.prototype = new Animal( "Wangwang");
この時点で、犬には名前と年齢の2つの属性があります。そして、ofオペレーターが犬に使用されている場合
コードコピーは次のとおりです。
console.log(dog instanceof animal); // 真実
console.log(犬の犬のインスタンス); // 間違い
これは相続を達成しますが、小さな問題があります
コードコピーは次のとおりです。
console.log(dog.prototype.constructor ===動物); // 真実
console.log(dog.prototype.constructor === dog); // 間違い
コンストラクターによって指摘されたオブジェクトが変更されたことがわかりますが、それは私たちの目的を満たしていません。私たちが新しいインスタンスが誰に属しているかを判断することはできません。したがって、1つの文を追加できます。
コードコピーは次のとおりです。
dog.prototype.constructor = dog;
もう一度見てみましょう:
コードコピーは次のとおりです。
console.log(dog instanceof animal); // 間違い
console.log(犬の犬のインスタンス); // 真実
終わり。この方法は、プロトタイプチェーンのメンテナンスのリンクであり、以下で詳しく説明します。 2。方法2この方法には利点と短所がありますが、欠点は利点を上回ります。最初にコードを見てください
コードコピーは次のとおりです。
<pre name = "code"> function animal(name){
this.name = name;
}
animal.prototype.setname = function(name){
this.name = name;
}
function dog(age){
this.age = age;
}
dog.prototype = animal.prototype;
これにより、プロトタイプのコピーが可能になります。
この方法の利点は、オブジェクトのインスタンス化(方法1と比較)を必要とせず、リソースを節約することです。短所も明らかです。上記と同じ問題に加えて、つまり、コンストラクターは親オブジェクトを指し、親オブジェクトがプロトタイプで宣言したプロパティと方法のみをコピーすることができます。つまり、上記のコードでは、動物オブジェクトの名前属性をコピーすることはできませんが、setNameメソッドをコピーできます。最も致命的なことは、子オブジェクトのプロトタイプの変更が親オブジェクトのプロトタイプに影響すること、つまり、両方のオブジェクトによって宣言されたインスタンスが影響を受けるということです。したがって、この方法は推奨されません。
プロトタイプチェーン
相続について書いた人は誰でも、相続が複数のレベルから継承できることを知っています。 JSでは、これはプロトタイプチェーンを形成します。上記の記事では、プロトタイプチェーンについても何度も言及したので、プロトタイプチェーンとは何ですか?インスタンスは、少なくともJavaScriptのオブジェクトシステムの基礎であるプロトタイプを指すプロト属性を持つ必要があります。ただし、このプロパティは目に見えないため、「内部プロトタイプチェーン」と呼び、コンストラクターのプロトタイプで構成される「コンストラクタープロトタイプチェーン」(つまり、通常は「プロトタイプチェーン」と呼ばれるもの)と区別します。上記のコードに従って、まず単純な継承関係を構築しましょう。
コードコピーは次のとおりです。
function animal(name){
this.name = name;
}
function dog(age){
this.age = age;
}
var Animal = new Animal( "Wangwang");
dog.prototype = animal;
var dog = new Dog(2);
リマインダーとして、前述のように、すべてのオブジェクトは空のオブジェクトを継承します。したがって、プロトタイプチェーンを作成します。
子オブジェクトのプロトタイプは、コンストラクタープロトタイプチェーンを形成している親オブジェクトのインスタンスを指していることがわかります。子のインスタンスの内側のプロトオブジェクトは、内部プロトタイプチェーンを形成する親オブジェクトを指すインスタンスでもあります。プロパティを見つける必要がある場合、コードは
コードコピーは次のとおりです。
function getattrfromobj(attr、obj){
if(typeof(obj)=== "object"){
var proto = obj;
while(proto){
if(proto.hasownproperty(attr)){
return proto [attr];
}
proto = proto .__ proto__;
}
}
未定義を返す;
}
この例では、犬の名前属性を探すと、犬のメンバーリストで検索されます。もちろん、犬のメンバーリストはアイテムの年齢にすぎないため、それは見つかりません。その後、プロトタイプチェーンに沿って検索を続けます。つまり、.protoによって指摘されたインスタンス、つまり動物では名前属性を見つけて返します。検索が存在しないプロパティである場合、動物で見つからない場合、.protoで検索し、空のオブジェクトを見つけてから.protoで検索し続け、空のオブジェクトの.protoはnullを指し、出口を探します。
プロトタイプチェーンのメンテナンス今、プロトタイプの継承について話したときに疑問を投げかけました。方法10を使用して継承を構築する場合、子オブジェクトインスタンスのコンストラクターは親オブジェクトを指します。これの利点は、コンストラクター属性を介してプロトタイプチェーンにアクセスできることであり、欠点も明らかです。インスタンスがそれ自体を指すべきオブジェクト、すなわち
コードコピーは次のとおりです。
(new obj())。prototype.constructor === obj;
次に、プロトタイププロパティを書き直した後、子オブジェクトによって生成されたインスタンスのコンストラクターはそれ自体を指しません!これは、コンストラクターの元の意図に反します。上記の解決策について言及しました:
コードコピーは次のとおりです。
dog.prototype = new Animal( "Wangwang");
dog.prototype.constructor = dog;
何も悪いことはないようです。しかし、実際には、親オブジェクトを見つけることができず、内部プロトタイプチェーンの.proto属性がアクセスできないため、プロトタイプチェーンに戻ることができないことがわかるため、これは新しい問題を引き起こします。したがって、Spidermonkeyは改善を提供します。__Proto__という名前のプロパティを作成したオブジェクトに追加します。これは、コンストラクターが使用するプロトタイプを常に指します。このようにして、コンストラクターの変更は__Proto__の値に影響を与えないため、コンストラクターを維持するのに便利です。
ただし、さらに2つの問題があります。
__Proto__は書き換え可能です。つまり、それを使用するとまだリスクがあります
__Proto__はSpidermonkeyの特別な処理であり、他のエンジン(JScriptなど)では使用できません。
プロトタイプのコンストラクタープロパティを維持し、サブクラスコンストラクター関数内のインスタンスのコンストラクタープロパティを初期化する別の方法があります。
コードは次のとおりです。子オブジェクトを書き直します
コードコピーは次のとおりです。
function dog(age){
this.constructor = arguments.callee;
this.age = age;
}
dog.prototype = new Animal( "Wangwang");
このようにして、子オブジェクトのすべてのインスタンスのコンストラクターはオブジェクトを正しく指し、プロトタイプのコンストラクターは親オブジェクトを指します。インスタンスが構築されるたびにコンストラクター属性を書き直す必要があるため、この方法は比較的非効率的ですが、この方法が以前の矛盾を効果的に解決できることは間違いありません。 ES5はこれを考慮し、この問題を完全に解決します:Object.getPrototypeof()は、コンストラクターにアクセスしたり、外部プロトタイプチェーンを維持せずにオブジェクトの実際のプロトタイプを取得するためにいつでも使用できます。したがって、前のセクションで述べたように、次のように書き換えることができます。
コードコピーは次のとおりです。
function getattrfromobj(attr、obj){
if(typeof(obj)=== "object"){
する {
var proto = object.getPrototypeof(dog);
if(proto [attr]){
return proto [attr];
}
}
while(proto);
}
未定義を返す;
}
もちろん、この方法はES5をサポートするブラウザでのみ使用できます。後方互換性を維持するには、以前の方法を検討する必要があります。より適切な方法は、これら2つの方法を統合してカプセル化することです。読者はこれが非常に得意だと思うので、ここではugさはありません。