L'une des caractéristiques de l'orientation objet est l'héritage. La plupart des langages de programmation orientés objet prennent en charge deux types d'héritage : l'héritage d'interface et l'héritage d'implémentation. L'héritage d'interface hérite uniquement des signatures de méthodes, tandis que l'héritage d'implémentation hérite des méthodes réelles. Étant donné que les fonctions n'ont pas de signature en JavaScript, l'héritage d'interface ne peut pas être implémenté. En JavaScript, l'héritage est principalement implémenté via la chaîne de prototypes.
Implémentation de l'héritage basé sur une chaîne de prototypes
L'idée de base de l'implémentation de l'héritage basé sur une chaîne de prototypes est d'utiliser des prototypes pour permettre à un type de référence d'hériter des propriétés et des méthodes d'un autre type de référence. Auparavant, nous avons présenté la relation entre les prototypes, les constructeurs et les instances d'objet, et analysé en détail leurs structures de modèle de mémoire. Nous utilisons l'exemple suivant pour analyser comment JavaScript implémente l'héritage basé sur la chaîne de prototypes.
// 创建父类 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();
Dans le code ci-dessus, nous créons d'abord une classe parent Parent et ajoutons la méthode showParentValue à son prototype.
Ensuite, nous avons créé une sous-classe Child et implémenté l'héritage en laissant la chaîne de prototypes de la sous-classe pointer vers la classe parent.
/** 实现继承的关键代码 **/Child.prototype = new Parent();
Ajoutez ensuite la méthode showChildValue dans la chaîne de prototypes de la sous-classe. Ensuite, un objet de sous-classe est créé. A ce moment, l'objet de sous-classe c peut appeler ses propres méthodes ou celles héritées de la classe parent.
Après avoir exécuté le code ci-dessus, le diagramme du modèle de mémoire de la sous-classe et de la classe parent est le suivant :
Comme nous pouvons le voir sur l'image ci-dessus , Lors de la création d'une sous-classe, l'attribut prototype de la sous-classe pointe vers l'objet prototype de la sous-classe. Lorsque nous faisons pointer le prototype de la sous-classe vers l'objet de la classe parent via l'instruction Child.prototype = new Parent(); À ce stade, il y aura un attribut _proto_ dans l'objet prototype de la sous-classe pointant vers l'objet prototype de la classe parent. L'objet prototype de la sous-classe d'origine (zone rouge sur l'image) est en réalité inutile.
Les méthodes que nous ajoutons dans le prototype de sous-classe seront ensuite ajoutées au nouveau prototype de sous-classe. Dans le même temps, les attributs de la classe parent seront également écrits dans le nouveau prototype de sous-classe.
Après avoir créé l'objet de sous-classe c, nous appelons la méthode showParentValue de la classe parent via l'objet c. Si l'objet c ne trouve pas cette méthode dans son propre espace, il recherchera dans le prototype de la sous-classe via l'attribut _proto_. Il ne trouvera pas non plus cette méthode, puis il ira au prototype de la classe parent via l'attribut _proto_. attribut dans le prototype. Pour rechercher, à ce moment, la méthode showParentValue est trouvée et exécutée correctement.
Le processus ci-dessus est le processus de mise en œuvre de l'héritage basé sur la chaîne de prototypes.
Modèle de mémoire complet basé sur une chaîne de prototypes pour implémenter l'héritage
Dans le modèle de mémoire ci-dessus basé sur une chaîne de prototypes pour implémenter l'héritage, nous décrivons en fait un objet de moins : Objet. Tous les types de référence héritent d'Object, et cet héritage est également implémenté via la chaîne de prototypes. Par conséquent, le modèle de mémoire complet basé sur la chaîne de prototypes pour implémenter l'héritage est le suivant :
Le prototype par défaut de toutes les fonctions est Object, donc le prototype par défaut contiendra un pointeur interne pointant vers Object.prototype. Il existe des méthodes hasOwnProperty, isPrototypeOf, propertyEmunerable, toLocaleString, toString et valueOf intégrées dans Object.prototype, donc nos types personnalisés hériteront de ces méthodes.
Choses à noter lors de l'utilisation de la chaîne de prototypes pour l'héritage
Lors de l'utilisation de la chaîne de prototypes pour l'héritage, veuillez faire attention aux problèmes suivants :
1. Vous ne pouvez pas réaffecter la chaîne de prototypes après avoir défini la chaîne de prototypes.
2. Les méthodes doivent être ajoutées ou remplacées après l'affectation dans la chaîne de prototypes.
Pour le premier point, regardons l'exemple suivant :
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)!