Implémentation du mécanisme d'héritage ECMAScript
- Page précédente Exemple de mécanisme d'héritage
- Page suivante Tutoriel avancé JavaScript
Mise en œuvre du mécanisme d'héritage
Pour réaliser le mécanisme d'héritage avec ECMAScript, vous pouvez commencer par la classe de base à hériter. Toutes les classes définies par les développeurs peuvent être utilisées comme classes de base. Pour des raisons de sécurité, les classes locales et les classes hôte ne peuvent pas être utilisées comme classes de base, ce qui permet de prévenir l'accès public au code compilé de niveau navigateur, car ce code peut être utilisé pour des attaques malveillantes.
Après avoir sélectionné la classe de base, vous pouvez créer ses sous-classes. L'utilisation de la classe de base dépend entièrement de vous. Parfois, vous pouvez vouloir créer une classe de base qui ne peut pas être utilisée directement, mais qui est utilisée pour fournir des fonctions génériques aux sous-classes. Dans ce cas, la classe de base est considérée comme une classe abstraite.
Bien que ECMAScript ne définisse pas strictement les classes abstraites comme d'autres langages, il crée parfois des classes qui ne peuvent pas être utilisées. Souvent, nous appelons ces classes des classes abstraites.
Les sous-classes créées hériteront de toutes les propriétés et méthodes de la classe parente, y compris les implémentations des constructeurs et des méthodes. N'oubliez pas que toutes les propriétés et méthodes sont publiques, donc les sous-classes peuvent accéder directement à ces méthodes. Les sous-classes peuvent également ajouter de nouvelles propriétés et méthodes qui n'existent pas dans la classe parente, ou surcharger les propriétés et méthodes de la classe parente.
Les méthodes d'héritage
Comme toutes les autres fonctionnalités, il existe plusieurs façons d'implémenter l'héritage dans ECMAScript. Cela est dû au fait que le mécanisme d'héritage dans JavaScript n'est pas spécifié de manière explicite, mais est réalisé par imitation. Cela signifie que tous les détails de l'héritage ne sont pas traités par l'interpréteur. En tant que développeur, vous avez le droit de décider de la méthode d'héritage la plus appropriée.
Nous vous présentons plusieurs méthodes d'héritage spécifiques.
Masquage d'objet
Lors de la conception initiale de ECMAScript, il n'était pas prévu de concevoir le masquage d'objet (object masquerading). Cela a évolué après que les développeurs ont commencé à comprendre le fonctionnement des fonctions, en particulier la manière d'utiliser le mot-clé this dans l'environnement de fonction.
Le principe est le suivant : le constructeur utilise le mot-clé this pour assigner toutes les propriétés et méthodes (c'est-à-dire en utilisant la méthode de déclaration de classe du constructeur). Comme le constructeur est une fonction, il peut rendre le constructeur ClassA une méthode de ClassB, puis l'appeler. ClassB recevra les propriétés et méthodes définies dans le constructeur de ClassA. Par exemple, vous pouvez définir ClassA et ClassB comme suit :
function ClassA(sColor) { this.color = sColor; this.sayColor = function () { alert(this.color); }; } function ClassB(sColor) { }
Souvenez-vous ? Le mot-clé this fait référence à l'objet créé par le constructeur actuel. Cependant, dans cette méthode, this fait référence à l'objet appartenant à cette méthode. Ce principe consiste à utiliser ClassA comme une fonction régulière pour établir un mécanisme d'héritage, plutôt que comme un constructeur. Vous pouvez réaliser le mécanisme d'héritage en utilisant le constructeur ClassB comme suit :
function ClassB(sColor) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; }
Dans ce segment de code, la méthode newMethod est attribuée à ClassA (n'oubliez pas que le nom de la fonction est simplement un pointeur vers elle). Ensuite, cette méthode est appelée, et les paramètres passés à la fonction de constructeur de ClassB sont sColor. La dernière ligne de code supprime la référence à ClassA, de sorte qu'elle ne pourra plus être appelée par la suite.
Toutes les nouvelles propriétés et méthodes doivent être définies après la suppression de la ligne de code de la méthode nouvelle. Sinon, elles pourraient masquer les propriétés et méthodes associées à la classe parente :
function ClassB(sColor, sName) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; this.name = sName; this.sayName = function () { alert(this.name); }; }
Pour prouver l'efficacité du code précédent, vous pouvez exécuter l'exemple suivant :
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); // Affiche "blue" objB.sayColor(); // Affiche "red" objB.sayName(); // Affiche "John"
Le masquage d'objet peut réaliser l'héritage multiple
Curieusement, le masquage d'objet peut soutenir l'héritage multiple. C'est-à-dire, une classe peut hériter de plusieurs classes parentes. Le mécanisme d'héritage multiple représenté par UML est montré dans l'image suivante :

Par exemple, si il existe deux classes ClassX et ClassY, ClassZ souhaite hériter de ces deux classes, vous pouvez utiliser le code suivant :
function ClassZ() { this.newMethod = ClassX; this.newMethod(); delete this.newMethod; this.newMethod = ClassY; this.newMethod(); delete this.newMethod; }
这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。
由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。
call() 方法
call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.call(obj, "The color is ", "a very nice color indeed.");
在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 obj 的 color 属性等于 blue。调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。
要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.call(this, sColor); this.name = sName; this.sayName = function () { alert(this.name); }; }
在这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。
apply() 方法
apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
Cet exemple est le même que le précédent, mais ici, on appelle la méthode apply(). Lors de l'appel à la méthode apply(), le premier paramètre reste obj, ce qui signifie que la valeur de this dans la fonction sayColor() doit être obj. Le second paramètre est un tableau composé de deux chaînes de caractères, correspondant aux paramètres sPrefix et sSuffix dans la fonction sayColor(). Le message généré est toujours "The color is blue, a very nice color indeed." et sera affiché.
Cette méthode est également utilisée pour remplacer les lignes d'affectation, d'appel et de suppression de la nouvelle méthode des trois premières lignes :
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, new Array(sColor)); this.name = sName; this.sayName = function () { alert(this.name); }; }
De la même manière, le premier paramètre reste this, et le second paramètre est un tableau contenant une seule valeur color. On peut transmettre l'objet entire arguments de ClassB en tant que second paramètre à la méthode apply() :
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, arguments); this.name = sName; this.sayName = function () { alert(this.name); }; }
Bien sûr, seules les classes parentes et les classes filles peuvent transmettre des objets de paramètres si l'ordre des paramètres est identique. Sinon, il est nécessaire de créer un tableau distinct et de placer les paramètres dans l'ordre correct. De plus, on peut utiliser la méthode call().
Chaîne de prototypes (prototype chaining)
Ce type d'héritage est utilisé dans ECMAScript pour la chaîne de prototypes. Le chapitre précédent a introduit la méthode de définition de la classe en utilisant prototype. La chaîne de prototypes étend cette méthode pour réaliser un mécanisme d'héritage de manière intéressante.
Comme nous l'avons appris au chapitre précédent, l'objet prototype est un modèle sur lequel se basent tous les objets à instancier. En d'autres termes, toutes les propriétés et méthodes de l'objet prototype sont transmises à toutes les instances de la classe. La chaîne de prototypes utilise cette fonctionnalité pour réaliser le mécanisme d'héritage.
Si l'on redéfinit la classe précédente en utilisant la méthode prototype, elle prendra la forme suivante :
function ClassA() { } ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB() { } ClassB.prototype = new ClassA();
L'extraordinaire de la méthode de prototype réside dans la ligne de code en bleu mis en évidence. Ici, la propriété prototype de ClassB est définie sur une instance de ClassA. Cela est très intéressant, car l'on souhaite obtenir toutes les propriétés et méthodes de ClassA, mais sans les ajouter une par une à l'attribut prototype de ClassB. Y a-t-il une meilleure méthode que de donner une instance de ClassA à l'attribut prototype ?
Attention :Appeler le constructeur de ClassA sans lui transmettre de paramètres. C'est la pratique standard dans la chaîne de prototypes. Assurez-vous que le constructeur n'a pas de paramètres.
De manière similaire à l'impersonation d'objet, toutes les propriétés et méthodes de la sous-classe doivent apparaître après l'affectation de l'attribut prototype, car toutes les méthodes affectées avant cela seront supprimées. Pourquoi ? Parce que l'attribut prototype est remplacé par un nouvel objet, et l'objet original ajoutant les méthodes sera détruit. Donc, pour ajouter l'attribut name et la méthode sayName() à la classe ClassB, le code suivant est utilisé :
function ClassB() { } ClassB.prototype = new ClassA(); ClassB.prototype.name = ""; ClassB.prototype.sayName = function () { alert(this.name); };
Vous pouvez tester ce code en exécutant l'exemple suivant :
var objA = new ClassA(); var objB = new ClassB(); objA.color = "blue"; objB.color = "red"; objB.name = "John"; objA.sayColor(); objB.sayColor(); objB.sayName();
En plus, dans la chaîne de prototypes, le fonctionnement de l'opérateur instanceof est également unique. Pour toutes les instances de ClassB, instanceof renvoie true pour ClassA et ClassB. Par exemple :
var objB = new ClassB(); alert(objB instanceof ClassA); //Affiche "true" alert(objB instanceof ClassB); //Affiche "true"
Dans le monde du type faible d'ECMAScript, c'est un outil extrêmement utile, mais il ne peut pas être utilisé lors de l'impersonation d'objet.
Les inconvénients de la chaîne de prototypes sont qu'elle ne supporte pas l'héritage multiple. N'oubliez pas que la chaîne de prototypes utilisera un autre type d'objet pour écraser l'attribut prototype de la classe.
Méthode de mélange
Cette manière d'hériter utilise le constructeur pour définir la classe, pas n'importe quel prototype. Le principal problème de l'impersonation d'objet est qu'il est nécessaire d'utiliser la méthode du constructeur, ce n'est pas le meilleur choix. Cependant, si l'on utilise la chaîne de prototypes, on ne peut pas utiliser de constructeur avec des paramètres. Comment les développeurs choisissent-ils ? La réponse est simple, les deux.
Dans le chapitre précédent, nous avons déjà expliqué que la meilleure manière de créer une classe est d'utiliser des constructeurs pour définir des attributs et des prototypes pour définir des méthodes. Cette méthode est également applicable au mécanisme d'héritage, où l'objet substitue l'héritage des attributs du constructeur et la chaîne de prototypes hérite des méthodes de l'objet prototype. En réécrivant l'exemple précédent de ces deux manières, le code suivant est obtenu :
function ClassA(sColor) { this.color = sColor; } ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB(sColor, sName) { ClassA.call(this, sColor); this.name = sName; } ClassB.prototype = new ClassA(); ClassB.prototype.sayName = function () { alert(this.name); };
Dans cet exemple, le mécanisme d'héritage est réalisé par deux lignes de code en bleu mis en évidence. Dans la première ligne mise en évidence, dans le constructeur de ClassB, l'objet substitue l'héritage de l'attribut sColor de la classe ClassA. Dans la deuxième ligne mise en évidence, les méthodes de la classe ClassA sont héritées via la chaîne de prototypes. Comme ce mélange utilise la chaîne de prototypes, l'opérateur instanceof peut toujours fonctionner correctement.
Le exemple suivant teste ce code :
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); // Affiche "blue" objB.sayColor(); // Affiche "red" objB.sayName(); // Affiche "John"
- Page précédente Exemple de mécanisme d'héritage
- Page suivante Tutoriel avancé JavaScript