ホームページ > 記事 > ウェブフロントエンド > JavaScript オブジェクト指向 - プロトタイプ チェーンに基づく継承
オブジェクト指向の特徴の 1 つは継承です。ほとんどのオブジェクト指向プログラミング言語は、インターフェイスの継承と実装の継承という 2 種類の継承をサポートしています。インターフェイスの継承はメソッドの署名のみを継承しますが、実装の継承は実際のメソッドを継承します。 JavaScript では関数に署名がないため、インターフェイスの継承を実装できません。 JavaScript では、継承は主にプロトタイプ チェーンを通じて実装されます。
プロトタイプ チェーンに基づく継承
プロトタイプ チェーンに基づく継承の基本的な考え方は、プロトタイプを使用して、ある参照型に別の参照型のプロパティとメソッドを継承させることです。以前に、プロトタイプ、コンストラクター、オブジェクト インスタンス間の関係を紹介し、それらのメモリ モデル構造を詳細に分析しました。次の例を使用して、JavaScript がプロトタイプ チェーンに基づいて継承をどのように実装するかを分析します。
// 创建父类 function Parent(){ this.parentValue = "Parent"; } // 在父类的原型中添加方法 Parent.prototype.showParentValue = function(){ alert(this.parentValue); } // 创建子类 function Child(){ this.childValue ="Child"; } // 实现继承,让子类Child的原型链指向Parent对象 Child.prototype = new Parent(); // 在子类的原型中添加方法 Child.prototype.showChildValue = function(){ alert(this.childValue); } // 创建子类对象 var c = new Child(); // 子类对象调用继承自父类的方法 c.showParentValue(); // 子类对象调用自己的方法 c.showChildValue();
上記のコードでは、まず親クラス Parent を作成し、そのプロトタイプに showParentValue メソッドを追加します。
次に、サブクラス Child を作成し、サブクラスのプロトタイプ チェーンが親クラスを指すようにすることで継承を実装しました。
/** 实现继承的关键代码 **/Child.prototype = new Parent();
次に、サブクラスのプロトタイプ チェーンに showChildValue メソッドを追加します。次に、サブクラス オブジェクトが作成されます。このとき、サブクラス オブジェクト c は、独自のメソッドまたは親クラスから継承したメソッドを呼び出すことができます。
上記のコードを実行した後、サブクラスと親クラスのメモリ モデル図は以下のようになります:
上の図から、サブクラスの作成時に、サブクラスのプロトタイプ属性が次を指していることがわかります。サブクラスのプロトタイプ オブジェクト。 Child.prototype = new Parent(); ステートメントを通じてサブクラスのプロトタイプが親クラス オブジェクトを指すようにする場合、実際にはサブクラスのプロトタイプ オブジェクトを書き換えます。このとき、サブクラスのプロトタイプ オブジェクトには、親クラスのプロトタイプ オブジェクトを指す _proto_ 属性が存在します。元のサブクラスのプロトタイプ オブジェクト (図の赤い領域) は、実際には役に立ちません。
サブクラス プロトタイプに追加したメソッドは、新しいサブクラス プロトタイプに追加されます。同時に、親クラスの属性も新しいサブクラス プロトタイプに書き込まれます。
サブクラス オブジェクト c を作成した後、オブジェクト c を通じて親クラスの showParentValue メソッドを呼び出します。オブジェクト c が独自の空間でこのメソッドを見つけられない場合、_proto_ 属性を通じてサブクラスのプロトタイプ内を検索し、このメソッドも見つからない場合は、_proto_ を通じて親クラスのプロトタイプに移動します。プロトタイプ内の属性を検索すると、この時点で showParentValue メソッドが見つかり、正しく実行されます。
上記のプロセスは、プロトタイプチェーンに基づいて継承を実装するプロセスです。
プロトタイプ チェーンに基づいて継承を実装する完全なメモリ モデル
プロトタイプ チェーンに基づいて継承を実装する上記のメモリ モデルでは、実際には 1 つ少ないオブジェクト、つまり Object を記述します。すべての参照型は Object から継承され、この継承もプロトタイプ チェーンを通じて実装されます。したがって、継承のプロトタイプ チェーン実装に基づく完全なメモリ モデルを以下の図に示します。
すべての関数のデフォルト プロトタイプは Object であるため、デフォルト プロトタイプには Object.prototype を指す内部ポインターが含まれます。 Object.prototype には組み込みの hasOwnProperty、isPrototypeOf、propertyEmunerable、toLocaleString、toString、および valueOf メソッドがあるため、カスタム型はこれらのメソッドを継承します。
プロトタイプチェーンを継承に使用する場合の注意事項
プロトタイプチェーンを継承に使用する場合は、次の問題に注意してください。
1. プロトタイプチェーンを設定した後は、プロトタイプチェーンを再割り当てすることはできません。
2. プロトタイプチェーンでの割り当て後にメソッドを追加またはオーバーライドする必要があります。
最初のポイントとして、次の例を見てみましょう:
function Parent(){ this.parentValue = "Parent"; } Parent.prototype.showParentValue = function(){ alert(this.parentValue); } function Child(){ this.childValue ="Child"; } //实现继承,让Child的原型链指向Parent对象 Child.prototype = new Parent(); //下面的操作重写了Child的原型 Child.prototype = { showChildValue:function(){ alert(this.value); } }
在上面的代码中,我们分别创建了一个父类和一个子类,并让子类的原型指向父类对象,实现继承。但是在这之后,我们又通过Child.prototype = {...}的方式重写了子类的原型,在原型的重写一文中我们已经讲过,重写原型实际上是使子类的原型重新指向一个新的子类原型,这个新的子类原型和父类之间并没有任何的关联关系,所以子类和父类之间此时不再存在继承关系。
对于第二点也很好理解,也来看一个例子:
function Parent(){ this.parentValue = "Parent"; } Parent.prototype.showParentValue = function(){ alert(this.parentValue); } function Child(){ this.childValue ="Child"; } //在实现继承之前为子类在原型中添加方法 Child.prototype.showChildValue = function(){ alert(this.childValue); } //实现继承,让Child的原型链指向Parent对象 Child.prototype = new Parent();
在上面的代码中,我们分别创建了父类和子类。在创建子类之后马上为子类在原型中添加一个方法,然后才让Child的原型链指向Parent对象,实现继承。
这样做的后果是实现继承之后,子类指向的是新的子类原型,而前面添加的方法是放置在原来的原型中的(内存模型图中的红色区域),所以在实现继承之后,子类对象将不再拥有这个方法,因为原来的原型现在已经没有作用了。
方法的覆盖及原型链继承的缺点
如果我们需要实现子类的方法来覆盖父类的方法,只需要在子类的原型中添加与父类同名的方法即可。
/** 覆盖父类中的showParentValue方法 **/ Child.prototype.showParentValue = function(){ alert("Override Parent method"); }
原型链虽然是否强大,可以实现继承,但是原型链也存在一些缺点。原型链继承的缺点主要有:
1、使用原型链进行继承最大的缺点是无法从子类中调用父类的构造函数,这样就没有办法把子类中的属性赋值到父类中。
2、如果父类中存在引用类型的属性,此时这个引用类型会添加到子类的原型中,当第一个对象修改这个引用之后,其它对象的引用同样会被修改。
原型链和原型在处理引用类型的值的时候存在同样的问题。我们在介绍原型的时候曾经举过一个使用引用类型的例子。在使用原型链时同样会有这个问题。来看下面的例子:
// 创建父类 function Parent(){ this.parentValue = "Parent"; //引用类型的属性 this.friends = ['Leon','Ada']; } // 在父类的原型中添加方法 Parent.prototype.showParentValue = function(){ console.info(this.name+"["+this.friends+"]"); } // 创建子类 function Child(){ this.childValue ="Child"; } // 实现继承,让子类Child的原型链指向Parent对象 Child.prototype = new Parent(); // 在子类的原型中添加方法 Child.prototype.showChildValue = function(){ console.info(this.name+"["+this.friends+"]"); } // 创建子类对象 var person1 = new Child(); person1.name = "Jack"; person1.friends.push('Tom'); var person2 = new Child(); person2.name = "John"; console.info(person2.friends);
在上面的代码中,在父类中有一个引用类型的数组对象属性friends,在子类实现继承之后,子类对象person1为它的friends添加了一个新的朋友,此时,新的朋友是被添加到父类的原型中的,所以在这之后创建的所有新的对象都会有一个新的朋友“Tom”。这就是引用类型属性存在的问题。
以上就是JavaScript面向对象-基于原型链实现继承的内容,更多相关内容请关注PHP中文网(www.php.cn)!