Cette traduction est incomplète. Aidez à traduire cet article depuis l'anglais.

Les présentations ayant été faites pour les concepts du JavaScript orienté objet, cet article détaille comment il est possible de créer une classe fille qui hérite des propriétés de sa classe mère. Nous verrons ensuite quelques conseils quant à l'utilisation du JavaScript orienté objet.

Pré-requis : Une connaissance générale de l'informatique, des notions d'HTML et CSS, une connaissance des bases en JavaScript (voir Premiers pas et Blocs de construction) ainsi que des notions de JavaScript orienté objet (JSOO) (voir Introduction aux objets).
Objectif : Comprendre comment implémenter l'héritage en JavaScript.

Héritage prototypique

Nous avons déjà vu le concept d'héritage en action, nous avons vu comment la chaine de prototypage fonctionnait, et comment les propriétés de cette chaine sont lues de manière ascendante. En revanche; nous n'avons utilisé pratiquement que quelques fonctionnalités déjà intégrées dans le navigateur pour le faire. Comment créer un objet JavaScript qui hérite d'un autre objet ?

Certains pensent que JavaScript n'est pas un véritable langage orienté objet. Dans les langages orientés objets classiques, on définit des classes objet et on peut ensuite définir laquelle hérite d'une autre (voir C++ inheritance en anglais pour des exemples simples). JavasScript utilise une approche différente : les objets héritant d'un autre n'ont pas de fonctionnalités copiées d'un autre objet, au lieu de ça, ils héritent des fonctionnalités via les liens de la chaîne de prototypage (on parle alors d'un héritage prototypique).

Voyons comment cela se passe avec un exemple concret.

Pour commencer

Tout d'abord, faites une copie du fichier oojs-class-inheritance-start.html (voir la démo). Vous y trouverez le constructeur Personne() que nous avons utilisé jusque là dans l'ensemble des modules, néanmoins il y a un léger changement : nous n'avons défini que les attributs au sein du constructeur.

function Personne(prenom, nom, age, genre, interets) {
  this.nom = {
    prenom,
    nom
  };
  this.age = age;
  this.genre = genre;
  this.interets = interets;
};

L'ensemble des méthodes est défini dans le prototype :

Person.prototype.salutations = function() {
  alert('Salut! Je suis ' + this.nom.prenom + '.');
};

Essayons de créer une classe Professeur similaire à celle que nous avons utilisée jusqu'ici dans les autres modules d'initiations à l'approche objet. Ainsi, cette classe hérite de Personne mais possède aussi :

  1. Un nouvel attribut matière — qui contiendra la matière que le professeur enseigne.
  2. Une méthode salutations un peu plus élaborée, qui sera un peu plus formelle que la méthode de base, cela sera plus approprié, lorsque le professeur s'adrressera à des étudiants, par exemple.

Définissons le constructeur Professeur()

La première chose à faire est de créer le constructeur Professeur() via l'ajout du code suivant :

function Professeur(prenom, nom, age, genre, interet, matiere) {
  Person.call(this, prenom, nom, age, genre, interet);

  this.matiere = matiere;
}

Cela ressemble beaucoup au constructeur Personne mais il y a quelque chose que nous n'avons pas encore vu : la fonction call(). Cette fonction permet d'appeler une fonction définie ailleurs dans le contexte actuel. Le premier paramètre spécifie la valeur de this que l'on souhaite utiliser lors que l'on utilisera la fonction, les paramètres suivants seront les paramètres qui pourront être passés en arguments lorsqu'elle sera appelée.

Nous voulons que le constructeur Professeur() aie les mêmes attributs que Personne(), nous les spécifions donc dans l'appel fait via la fonction call().

La dernière ligne au sein du constructeur sert simplement à définir l'attribut matière que les professeurs enseignent, ce qui n'est pas valable pour les personnes génériques.

Notez que nous aurions très bien pu écrire tout simplement ceci :

function Professeur(prenom, nom, age, genre, interets, matiere) {
  this.nom_complet = {
    prenom,
    nom
  };
  this.age = age;
  this.genre = genre;
  this.interets = interets;
  this.matiere = matiere;
}

Cependant cela aurait eu pour effet de redéfinir les attributs à nouveau, sans les hériter de Personne(), ce qui n'est pas vraiment le but que nous voulons atteindre lorsque l'on parle de l'héritage, cela rajoute aussi des lignes de code inutiles.

Définir le prototype de Professeur() et son constructeur référent.

Pour le moment tout va bien, mais nous avons un petit problème, nous avons défini un constructeur, en revanche il nous manque le lien avec le constructeur Personne() ainsi que son prototype. Vous pouvez par exemple entrer Professeur.prototype.constructor dans la console JavaScript pour voir ce qu'il en est.

Notre classe Professeur() doit hériter des méthodes définies dans le prototype de Personne(). Comment faire cela ?

  1. Ajoutez la ligne suivante à la suite du bloc de code que nous venons d'ajouter :
    Professeur.prototype = Object.create(Personne.prototype);
    On remarque que notre ami create() vient nous aider à nouveau. Dans ce cas, on l'utilise afin de créer un nouvel objet basé sur la valeur de l'objet Personne.prototype (le prototype du constructeur Personne()). On lui donne ensuite la valeur de Professeur.prototype. Cela signifie que Professeur.prototype va désormais hériter des méthodes définies dans Personne.prototype.
  2. Après avoir ajouté la ligne précédente le constructeur du prototype de Professeur() est désormais équivalent à celui de Personne(), en effet nous avons défini le prototype comme une copie de Personne.prototype ! Essayez, après avoir sauvegardé votre code, d'entrer Professeur.prototype.constructor dans la console pour vérifier.
  3. Cela peut  devenir problématique, autant le corriger dès maintenant. C'est possible via l'ajout de la ligne de code suivante à la fin :
    Professeur.prototype.constructor = Professeur;
  4. A présent, si vous sauvegardez et rafraichissez en écrivant Professeur.prototype.constructor, cela devrait retourner Professeur(), et en plus nous héritons maintenant de Person()!

Donner au prototype de Professeur() une nouvelle fonction saluer()

Pour terminer notre code, nous devons définir une nouvelle fonction saluer() sur le constructeur de Professeur().

La façon la plus facile d'accomplir cela est de la définir sur le prototype de Professeur() — ajoutons ceci à la suite de vorte code:

Professeur.prototype.saluer = function() {
  var prefix;

  if (this.genre === 'mâle' || this.genre === 'Mâle' || this.genre === 'm' || this.genre === 'M') {
    prefix = 'M.';
  } else if (this.genre === 'femelle' || this.genre === 'Femelle' || this.genre === 'f' || this.genre === 'F') {
    prefix = 'Mme';
  } else {
    prefix = '';
  }

  alert('Bonjour. Mon nom est ' + prefix + ' ' + this.nom_complet.nom + ', et j\'enseigne ' + this.matiere + '.');
};

Ceci affiche la salutation du professeur, qui utilise le titre de civilité approprié à son genre, au moyen d'une instruction conditionnelle.

Exécuter l'exemple

Une fois tout le code saisi, essayez de créer une instance d'objet Professeur en ajoutant à la fin de votre JavaScript (ou à l'endroit de votre choix):

var professeur1 = new Professeur('Cédric', 'Villani', 44, 'm', ['football', 'cuisine'], 'les mathématiques');

Sauvegardez et actualisez, et essayez d'accéder aux propriétés et méthodes de votre nouvel objet professeur1, par example :

professeur1.nom_complet.nom;
professeur1.interets[0];
professeur1.bio();
professeur1.matiere;
professeur1.saluer();

These should all work just fine; the first three access members that were inherited from the generic Person() constructor (class), while the last two access members that are only available on the more specialized Teacher() constructor (class).

Note: If you have trouble getting this to work, compare your code to our finished version (see it running live also).

The technique we covered here is not the only way to create inheriting classes in JavaScript, but it works OK, and it gives you a good idea about how to implement inheritance in JavaScript.

You might also be interested in checking out some of the new ECMAScript features that allow us to do inheritance more cleanly in JavaScript (see Classes). We didn't cover those here, as they are not yet supported very widely across browsers. All the other code constructs we discussed in this set of articles are supported as far back as IE9 or earlier, and there are ways to achieve earlier support than that.

A common way is to use a JavaScript library — most of the popular options have an easy set of functionality available for doing inheritance more easily and quickly. CoffeeScript for example provides class, extends, etc.

A further exercise

In our OOP theory section, we also included a Student class as a concept, which inherits all the features of Person, and also has a different greeting() method to Person that is much more informal than the Teacher's greeting. Have a look at what the student's greeting looks like in that section, and try implementing your own Student() constructor that inherits all the features of Person(), and implements the different greeting() function.

Note: If you have trouble getting this to work, have a look at our finished version (see it running live also).

Object member summary

To summarize, you've basically got three types of property/method to worry about:

  1. Those defined inside a constructor function that are given to object instances. These are fairly easy to spot — in your own custom code, they are the members defined inside a constructor using the this.x = x type lines; in built in browser code, they are the members only available to object instances (usually created by calling a constructor using the new keyword, e.g. var myInstance = new myConstructor()).
  2. Those defined directly on the constructor themselves, that are available only on the constructor. These are commonly only available on built-in browser objects, and are recognized by being chained directly onto a constructor, not an instance. For example, Object.keys().
  3. Those defined on a constructor's prototype, which are inherited by all instances and inheriting object classes. These include any member defined on a Constructor's prototype property, e.g. myConstructor.prototype.x().

If you are not sure which is which, don't worry about it just yet — you are still learning, and familiarity will come with practice.

When would you use inheritance in JavaScript?

Particularly after this last article, you might be thinking "woo, this is complicated". Well, you are right, prototypes and inheritance represent some of the most complex aspects of JavaScript, but a lot of JavaScript's power and flexibility comes from its object structure and inheritance, and it is worth understanding how it works.

In a way, you use inheritance all the time — whenever you use various features of a WebAPI , or methods/properties defined on a built-in browser object that you call on your strings, arrays, etc., you are implicitly using inheritance.

In terms of using inheritance in your own code, you probably won't use it that often, especially to begin with, and in small projects — it is a waste of time to use objects and inheritance just for the sake of it, when you don't need them. But as your code bases get larger, you are more likely to find a need for it. If you find yourself starting to create a number of objects that have similar features, then creating a generic object type to contain all the shared functionality and inheriting those features in more specialized object types can be convenient and useful.

Note: Because of the way JavaScript works, with the prototype chain, etc., the sharing of functionality between objects is often called delegation — the specialized objects delegate that functionality to the generic object type. This is probably more accurate than calling it inheritance, as the "inherited" functionality is not copied to the objects that are doing the "inheriting". Instead it still remains in the generic object.

When using inheritance, you are advised to not have too many levels of inheritance, and to keep careful track of where you define your methods and properties. It is possible to start writing code that temporarily modifies the prototypes of built-in browser objects, but you should not do this unless you have a really good reason. Too much inheritance can lead to endless confusion, and endless pain when you try to debug such code.

Ultimately, objects are just another form of code reuse, like functions or loops, with their own specific roles and advantages. If you find yourself creating a bunch of related variables and functions and want to track them all together and package them neatly, an object is a good idea. Objects are also very useful when you want to pass a collection of data from one place to another. Both of these things can be achieved without use of constructors or inheritance. If you only need a single instance of an object, then you are probably better off just using an object literal, and you certainly don't need inheritance.

Summary

This article has covered the remainder of the core OOJS theory and syntax that we think you should know now. At this point you should understand JavaScript object and OOP basics, prototypes and prototypal inheritance, how to create classes (constructors) and object instances, add features to classes, and create subclasses that inherit from other classes.

In the next article we'll have a look at how to work with JavaScript Object Notation (JSON), a common data exchange format written using JavaScript objects.

See also

  • ObjectPlayground.com — A really useful interactive learning site for learning about objects.
  • Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
  • You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.

Étiquettes et contributeurs liés au document

Étiquettes : 
 Contributeurs à cette page : Yopai, AntrHaxx, MartyO256, Alpha
 Dernière mise à jour par : Yopai,