C や Java などのクラスベースのプログラミング言語とは異なり、JavaScript の継承はプロトタイプベースです。同時に、JavaScript は非常に柔軟な言語であるため、継承を実装する方法は数多くあります。
最初の基本概念は、コンストラクターとプロトタイプ チェーンについてです。親オブジェクトのコンストラクターは Parent と呼ばれ、子オブジェクトのコンストラクターは Child と呼ばれ、対応する親オブジェクトと子オブジェクトはそれぞれ親と子です。
オブジェクトには隠し属性 [[prototype]] (プロトタイプではないことに注意) があります。Chrome ではこれは __proto__ ですが、一部の環境ではこのオブジェクトのプロトタイプを指します。オブジェクトのプロパティまたはメソッドにアクセスする場合、最初にオブジェクトのすべてのプロパティが検索され、見つからない場合は、プロトタイプ オブジェクトのプロパティが [[prototype]] に従って、見つかるまでプロトタイプ チェーンに沿って段階的に検索されます。それ以外の場合は、未定義を返します。
1. プロトタイプチェーンの継承:
プロトタイプ チェーンは、JavaScript で継承を実装するデフォルトの方法です。子オブジェクトに親オブジェクトを継承させたい場合、最も簡単な方法は、子オブジェクトのコンストラクターのプロトタイプ属性を親オブジェクトのインスタンスに指定することです。
関数 Parent() {}
関数 Child() {}
Child.prototype = new Parent()
現時点では、Child のプロトタイプ属性は書き換えられ、新しいオブジェクトを指していますが、この新しいオブジェクトのコンストラクター属性は Child を正しく指していません。JS エンジンはこの作業を自動的に完了しないため、手動で行う必要があります。 Child のプロトタイプ オブジェクトのコンストラクター プロパティは Child:
を再ポイントします。
Child.prototype.constructor = Child
上記は JavaScript のデフォルトの継承メカニズムで、再利用する必要があるプロパティとメソッドをプロトタイプ オブジェクトに移行し、再利用できない部分をオブジェクト独自のプロパティとして設定します。ただし、この継承メソッドには新しいインスタンスが必要です。プロトタイプ オブジェクトの方が効率的です。
2. プロトタイプ継承 (非プロトタイプ チェーン):
前のメソッドでプロトタイプ オブジェクト インスタンスを繰り返し作成する問題を回避するために、子オブジェクト コンストラクターのプロトタイプを親オブジェクト コンストラクターのプロトタイプに直接ポイントすることができます。これにより、Parent 内のすべてのプロパティとメソッドが指定されます。 .prototype は再利用することもできます。同時に、プロトタイプ オブジェクト インスタンスを繰り返し作成する必要はありません:
Child.prototype = 親.prototype
Child.prototype.constructor = Child
ただし、JavaScript ではオブジェクトが参照型として存在することがわかっています。このメソッドは、実際には Child.prototype と Parent.prototype に保存されたポインターを同じオブジェクトにポイントします。したがって、子オブジェクトのプロトタイプを使用する場合は、プロパティを拡張します。後で継承を継続するために、親オブジェクトのプロトタイプも書き換えられます。これは、ここにはプロトタイプ オブジェクトのインスタンスが常に 1 つしかないためです。これは、この継承メソッドの欠点でもあります。
3. 一時コンストラクターの継承:
上記の問題を解決するには、一時コンストラクターを中間層として使用できます。子オブジェクト プロトタイプに対するすべての操作は、一時コンストラクターのインスタンス上で完了し、親オブジェクト プロトタイプには影響しません。 🎜 >
var F = function() {}
F.prototype = 親.プロトタイプ
Child.prototype = new F()
Child.prototype.constructor = Child
同時に、子オブジェクトの親クラス プロトタイプの属性にアクセスするために、uber などの子オブジェクト コンストラクターに親オブジェクト プロトタイプを指す属性を追加できます。 child.constructor.uber を通じて親プロトタイプ オブジェクトに直接アクセスできます。
上記の作業を関数にカプセル化することができ、今後この関数を呼び出すことで、この継承メソッドを簡単に実装できます。
function extend(子, 親) {
var F = function() {}
F.prototype = 親.プロトタイプ
Child.prototype = new F()
Child.prototype.constructor = Child
Child.uber = Parent.prototype
}
次に、次のように呼び出すことができます:
extend(犬、動物)
4. 属性コピー:
この継承メソッドは基本的にプロトタイプ チェーンの関係を変更しませんが、親プロトタイプ オブジェクトのすべての属性を子オブジェクト プロトタイプに直接コピーします。もちろん、ここでのコピーは基本的なデータ型とオブジェクト型にのみ適用されます。参照渡しのみをサポートします。
function extend2(子, 親) {
var p = 親.プロトタイプ
var c = Child.prototype
for (var i in p) {
c[i] = p[i]
}
c.uber = p
}
このメソッドは一部のプロトタイプ属性を再構築するため、オブジェクトを構築する際の効率は低くなりますが、プロトタイプ チェーンの検索を減らすことができます。ただし、個人的には、この方法の利点は明らかではないと感じています。
5. オブジェクト間の継承:
コンストラクター間の継承方法に加えて、コンストラクターを使用せずにオブジェクト間で直接継承することもできます。つまり、浅いコピーと深いコピーを含め、オブジェクト属性を直接コピーします。
浅いコピー:
継承されるオブジェクトを受け入れ、同時に新しい空のオブジェクトを作成し、継承されるオブジェクトのプロパティを新しいオブジェクトにコピーして、新しいオブジェクトを返します:
function extendCopy(p) {
var c = {}
for (var i in p) {
c[i] = p[i]
}
c.uber = p
c
を返します
}
コピーが完了したら、新しいオブジェクトで書き換える必要がある属性を手動で書き換えることができます。
ディープコピー:
浅いコピーの問題も明らかです。オブジェクト型の属性をコピーすることはできませんが、この問題を解決するには、深いコピーを使用する必要があります。ディープ コピーの焦点は、コピーの再帰呼び出しにあります。オブジェクト型のプロパティが検出されると、対応するオブジェクトまたは配列が作成され、基本型の値が 1 つずつコピーされます。
関数 deepCopy(p, c) {
c = c || {}
for (var i in p) {
If (p.hasOwnProperty(i)) {
If (typeof p[i] === 'オブジェクト') {
c[i] = Array.isArray(p[i]) [] : {}
deepCopy(p[i], c[i])
} else {
c[i] = p[i]
}
}
}
c
を返します
}
ES5 Array.isArray() メソッドは、パラメータが配列であるかどうかを判断するために使用されます。このメソッドを実装していない環境では、シムを手動でカプセル化する必要があります。
Array.isArray = function(p) {
配列の p インスタンスを返します
}
ただし、異なるフレームワークの配列変数は、instanceof 演算子を使用して判断することはできませんが、この状況は比較的まれです。
6. プロトタイプの継承:
親オブジェクトの助けを借りて、コンストラクターを通じて親オブジェクトによってプロトタイプされた新しいオブジェクトを作成します。
関数オブジェクト(o) {
var n
関数 F() {}
F.プロトタイプ = o
n = 新しい F()
n.uber = o
n
を返します
}
ここでは、親オブジェクトを子オブジェクトのプロトタイプとして直接設定するのが ES5 の Object.create() メソッドです。
7. プロトタイプ継承と属性コピーの混合使用:
プロトタイプの継承メソッドでは、渡された親オブジェクトに基づいて子オブジェクトが構築されます。同時に、親オブジェクトによって提供されるプロパティに加えて、コピーする必要がある追加のオブジェクトを渡すことができます。
関数 objectPlus(o, スタッフ) {
var n
関数 F() {}
F.プロトタイプ = o
n = 新しい F()
n.uber = o
for (var i in things) {
n[i] = スタッフ[i]
}
n
を返します
}
8. 多重継承:
このメソッドには、プロトタイプ チェーンの操作は含まれません。属性をコピーする必要がある複数のオブジェクトが渡され、すべての属性が順番にコピーされます。
関数 multi() {
var n = {}、スタッフ、i = 0、
len = argument.length
for (i = 0; i
もの = 引数[i]
for (var key in スタッフ) {
n[i] = スタッフ[i]
}
}
n
を返します
}
オブジェクトは、渡された順序に従って順番にコピーされます。つまり、後で渡されたオブジェクトに前のオブジェクトと同じプロパティが含まれている場合、後者は前者を上書きします。
9. コンストラクターの借用:
JavaScript の call() メソッドと apply() メソッドは非常に使いやすく、メソッドの実行コンテキストを変更する機能も継承の実装に役立ちます。いわゆるコンストラクターの借用とは、子オブジェクト コンストラクターで親オブジェクトのコンストラクターを借用して、これを操作することを指します:
関数 Parent() {}
Parent.prototype.name = '親'
関数 Child() {
Parent.apply(this, 引数)
}
var child = new Child()
console.log(child.name)
この方法の最大の利点は、サブオブジェクトのコンストラクターで、サブオブジェクト自体のプロパティが完全に再構築され、参照型変数も参照ではなく新しい値を生成するため、サブオブジェクトに対するあらゆる操作が実行されることです。 object どちらも親オブジェクトには影響しません。
このメソッドの欠点は、子オブジェクトの構築プロセス中に new 演算子が使用されないため、子オブジェクトは親プロトタイプ オブジェクトの属性を継承しないことです。子は未定義になります。
この問題を解決するには、子オブジェクト コンストラクターのプロトタイプを親オブジェクトのインスタンスに手動で再度設定します。
Child.prototype = new Parent()
しかし、これにより別の問題が発生します。つまり、親オブジェクトのコンストラクターが 2 回呼び出されます。1 回目は親オブジェクトのコンストラクターの借用プロセス中に、もう 1 回目はプロトタイプの継承プロセス中に呼び出されます。
この問題を解決するには、親オブジェクトのコンストラクターへの呼び出しを削除する必要があります。コンストラクターの借用は省略できないため、プロトタイプの継承を実装する別の方法は、反復的にコピーすることのみです。
extend2(子, 親)
以前に実装された extend2() メソッドを使用するだけです。