真の意味では、JavaScriptはオブジェクト指向言語ではなく、従来の継承方法を提供しませんが、継承を達成するために提供するプロトタイププロパティを使用して、プロトタイプ継承の方法を提供します。
プロトタイプとプロトタイプチェーン
プロトタイプの継承について話す前に、最初にプロトタイプとプロトタイプチェーンについて話す必要があります。結局のところ、これはプロトタイプの継承を実現するための基礎です。
JavaScriptでは、各関数には独自のプロトタイプを指すプロトタイプ属性のプロトタイプがあり、この関数によって作成されたオブジェクトには、このプロトタイプを指す__Proto__属性もあります。関数のプロトタイプはオブジェクトであるため、このオブジェクトには独自のプロトタイプを指している__Proto__も備えているため、オブジェクトオブジェクトのプロトタイプまでレイヤーごとに深くなり、プロトタイプチェーンが形成されます。次の図は、JavaScriptのプロトタイプとプロトタイプチェーンの関係を非常によく説明しています。
各関数は関数によって作成されたオブジェクトであるため、各関数には関数関数のプロトタイプを指す__Proto__属性もあります。ここでは、実際にプロトタイプチェーンを形成するのは、関数のプロトタイプ属性ではなく、各オブジェクトの__Proto__属性であり、非常に重要であることをここで指摘する必要があります。
プロトタイプの継承
基本モード
コードコピーは次のとおりです。
var parent = function(){
this.name = 'parent';
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(){
this.name = 'child';
};
child.prototype = new Parent();
var parent = new Parent();
var child = new Child();
console.log(parent.getName()); //親
console.log(child.getname()); //子供
これは、サブクラスのオブジェクトが親クラスのプロパティと親クラスコンストラクターのプロトタイプにアクセスできるように、親クラスのオブジェクトをサブクラスコンストラクターのプロトタイプに直接割り当てるプロトタイプ継承を実装する最も簡単な方法です。この方法のプロトタイプ継承図は次のとおりです。
この方法の利点は明らかであり、実装は非常に単純であり、特別な作戦は必要ありません。短所も明らかです。サブクラスが親クラスコンストラクターと同じ初期化アクションを実行する必要がある場合、サブクラスコンストラクターの親クラスで操作を繰り返す必要があります。
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
this.name = name || '子供' ;
};
child.prototype = new Parent();
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(parent.getName()); // myParent
console.log(child.getname()); // mychild
上記の状況では、名前属性を初期化する必要があります。初期化作業が増加し続けると、この方法は非常に不便です。したがって、以下を改善する方法があります。
コンストラクターを借ります
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
parent.Apply(これ、引数);
};
child.prototype = new Parent();
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(parent.getName()); // myParent
console.log(child.getname()); // mychild
上記の方法は、サブクラスコンストラクターの親クラスコンストラクターに呼び出しを適用することにより、同じ初期化作業を実行するため、親クラスでどれだけの初期化作業が行われても、サブクラスは同じ初期化作業を実行できます。ただし、上記の実装には別の問題があります。親クラスのコンストラクターは、サブクラスコンストラクターで1回、サブクラスのプロトタイプで1回実行されたため、これは多くの冗長性であるため、改善する必要があります。
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
parent.Apply(これ、引数);
};
child.prototype = parent.prototype;
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(parent.getName()); // myParent
console.log(child.getname()); // mychild
このようにして、サブクラスコンストラクターで親クラスコンストラクターを1回実行する必要があり、同時に親クラスのプロトタイプのプロパティを継承できると同時に。これは、プロトタイプの元の意図に沿ったものです。これは、プロトタイプで再利用する必要があるコンテンツを配置することであり、プロトタイプでの再利用可能なコンテンツのみを継承します。上記の方法のプロトタイプ図は次のとおりです。
一時的なコンストラクターモード(聖杯モード)
上記のコンストラクターパターンを借りたバージョンにはまだ問題があります。親クラスのプロトタイプをサブクラスのプロトタイプに直接割り当てます。これは問題を引き起こします。つまり、サブクラスのプロトタイプが変更された場合、変更は親クラスのプロトタイプにも影響し、親クラスオブジェクトに影響します。これは間違いなく誰もが望んでいるものではありません。この問題を解決するために、一時的なコンストラクターパターンが利用可能です。
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
parent.Apply(これ、引数);
};
var f = new Function(){};
f.prototype = parent.prototype;
child.prototype = new f();
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(parent.getName()); // myParent
console.log(child.getname()); // mychild
この方法のプロトタイプ継承図は次のとおりです。
親クラスのプロトタイプと子クラスのプロトタイプの間に一時的なコンストラクターFを追加することで、子クラスのプロトタイプと親クラスのプロトタイプとの関係が遮断されているため、子クラスのプロトタイプが変更されたときに親クラスのプロトタイプが影響を受けないことが簡単にわかります。
私の方法
聖杯モードは「JavaScriptモード」で終わりますが、上記の方法に関係なく、発見するのは簡単ではない問題があります。 「親」のプロトタイププロパティにOBJオブジェクトリテラル属性を追加したことがわかりますが、これは役に立たなかったことがわかります。聖杯モードに基づいて、次の状況を見てみましょう。
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
parent.Apply(これ、引数);
};
var f = new Function(){};
f.prototype = parent.prototype;
child.prototype = new f();
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(child.obj.a); // 1
console.log(parent.obj.a); // 1
child.obj.a = 2;
console.log(child.obj.a); // 2
console.log(parent.obj.a); // 2
上記の場合、子オブジェクトOBJ.Aを変更すると、親クラスのプロトタイプのOBJ.Aも変更され、共有プロトタイプと同じ問題が発生します。これは、child.obj.aにアクセスするときに、プロトタイプチェーンに従い、親クラスのプロトタイプを見つけてからOBJ属性を見つけてからOBJ.Aを変更するために起こります。次の状況を見てみましょう。
コードコピーは次のとおりです。
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a:1};
var child = function(name){
parent.Apply(これ、引数);
};
var f = new Function(){};
f.prototype = parent.prototype;
child.prototype = new f();
var parent = new Parent( 'myparent');
var child = new Child( 'mychild');
console.log(child.obj.a); // 1
console.log(parent.obj.a); // 1
child.obj.a = 2;
console.log(child.obj.a); // 2
console.log(parent.obj.a); // 2
ここには重要な問題があります。オブジェクトがプロトタイプ内のプロパティにアクセスすると、プロトタイプのプロパティはオブジェクトに対して読み取りのみです。つまり、子オブジェクトはOBJオブジェクトを読み取ることができますが、プロトタイプのOBJオブジェクト参照は変更できません。したがって、子供がOBJを修正する場合、プロトタイプのOBJには影響しません。それは、親プロトタイプのOBJ属性を上書きする独自のオブジェクトにOBJ属性を追加するだけです。子オブジェクトがOBJ.Aを変更すると、最初にプロトタイプのOBJへの参照を読み取ります。この時点で、child.obj and parent.prototype.objは同じオブジェクトを指しているため、子どものobj.aの変更はparent.prototype.obj.aの値に影響し、したがって親クラスのオブジェクトに影響します。 AngularJSの$スコープネストの継承方法は、JavasRiptのプロトタイプ継承をモデリングすることにより実装されます。
上記の説明によれば、サブクラスオブジェクトにアクセスされるプロトタイプが親クラスのプロトタイプと同じである限り、上記の状況が発生します。したがって、親クラスのプロトタイプをコピーしてから、サブクラスのプロトタイプに割り当てることができます。このようにして、サブクラスがプロトタイプのプロパティを変更する場合、親クラスのプロトタイプのコピーのみを変更し、親クラスのプロトタイプには影響しません。特定の実装は次のとおりです。
コードコピーは次のとおりです。
var deepclone = function(source、target){
ソース=ソース|| {};
var toStr = object.prototype.toString、
arrstr = '[object array]';
for(var i in source){
if(source.hasownproperty(i)){
var item = source [i];
if(typeof item === 'object'){
ターゲット[i] =(toStr.Apply(item).tolowercase()=== arrst):[]? {};
deepclone(item、ターゲット[i]);
}それ以外{
deepclone(item、ターゲット[i]);
}
}
}
ターゲットを返します。
};
var parent = function(name){
this.name = name || '親' ;
};
parent.prototype.getName = function(){
this.nameを返します。
};
parent.prototype.obj = {a: '1'};
var child = function(name){
parent.Apply(これ、引数);
};
child.prototype = deepclone(parent.prototype);
var child = new Child( 'child');
var parent = new Parent( 'parent');
console.log(child.obj.a); // 1
console.log(parent.obj.a); // 1
child.obj.a = '2';
console.log(child.obj.a); // 2
console.log(parent.obj.a); // 1
上記のすべての考慮事項に基づいて、JavaScript継承の特定の実装は次のとおりです。子と親が関数が考慮される場合のみ:
コードコピーは次のとおりです。
var deepclone = function(source、target){
ソース=ソース|| {};
var toStr = object.prototype.toString、
arrstr = '[object array]';
for(var i in source){
if(source.hasownproperty(i)){
var item = source [i];
if(typeof item === 'object'){
ターゲット[i] =(toStr.Apply(item).tolowercase()=== arrst):[]? {};
deepclone(item、ターゲット[i]);
}それ以外{
deepclone(item、ターゲット[i]);
}
}
}
ターゲットを返します。
};
var extend = function(親、子){
子=子||関数(){} ;
if(parent === undefined)
子供を返します。
//親クラスコンストラクターを借ります
child = function(){
parent.Apply(これ、引数);
};
//ディープコピーを介して親クラスのプロトタイプを継承します
child.prototype = deepclone(parent.prototype);
// Constructor属性をリセットします
child.prototype.constructor = child;
};
要約します
実際、JavaScriptに継承を実装することは非常に柔軟で多様であり、最良の方法はありません。さまざまな継承方法を実装する必要があります。さまざまなニーズに応じて実装する必要があります。最も重要なことは、JavaScriptに継承を実装する原則、つまりプロトタイプとプロトタイプチェーンの問題を理解することです。これらを理解している限り、自分で継承を簡単に実装できます。