Deux classes qui partagent quatre-vingts pour cent de leur code posent un problème connu : la moindre correction doit être faite deux fois, et l'une des deux finit toujours par être oubliée.
extends est la réponse du langage à cette situation. Il établit une filiation entre deux classes, de sorte que le code commun ne s'écrive qu'une fois.
Définition
extends place une class sous une autre. La classe fille reçoit automatiquement les méthodes du parent, peut redéfinir celles qui doivent changer, et en ajouter de nouvelles.
class Utilisateur {
constructor(nom) { this.nom = nom; }
saluer() { return "Bonjour " + this.nom; }
}
class Admin extends Utilisateur {
saluer() { return super.saluer() + " (admin)"; }
supprimer(cible) { return this.nom + " supprime " + cible; }
}
const admin = new Admin("Camille");
console.log(admin.saluer()); // Bonjour Camille (admin)
console.log(admin instanceof Utilisateur); // trueLa classe Admin n'a pas de constructeur : celui du parent s'applique tel quel. Sa méthode saluer remplace celle du parent, tout en la réutilisant grâce à super. Et un administrateur reste un utilisateur aux yeux de instanceof, ce qui permet de le passer partout où un utilisateur est attendu.
Ce que la classe fille reçoit
L'héritage porte sur plus de choses qu'on ne l'imagine.
| Ce qui est hérité | Précision |
|---|---|
| Les méthodes du parent | Disponibles directement sur l'instance |
| Les accesseurs get et set | Au même titre que les méthodes |
| Les membres static | Accessibles sur la classe fille elle-même |
| Le constructeur | Uniquement si la fille n'en déclare pas |
Les champs privés du parent, en revanche, restent hors d'atteinte : une classe fille ne peut pas lire un champ préfixé par un dièse déclaré au-dessus d'elle. Pour exposer une donnée interne à la descendance, il faut un accesseur.
Hériter des classes natives
Le mot-clé fonctionne avec les classes du langage, et le cas le plus utile de loin est Error.
class ErreurHttp extends Error {
constructor(statut) {
super("Requête refusée avec le code " + statut);
this.name = "ErreurHttp";
this.statut = statut;
}
}
const erreur = new ErreurHttp(404);
console.log(erreur instanceof Error); // true
console.log(erreur.statut); // 404Le bloc qui rattrape peut alors trier les erreurs par type au lieu de comparer des messages. Le même mécanisme s'applique à Array (tableau) ou à Map, même si le besoin s'y présente beaucoup plus rarement.
Une classe fille qui déclare son propre constructeur doit appeler super() avant d'utiliser this. Sans cet appel, le langage lève une erreur au moment de la construction : l'objet n'existe pas encore.
Questions fréquentes
Peut-on hériter de plusieurs classes à la fois ?
Non, JavaScript n'admet qu'un seul parent par classe. Quand plusieurs comportements indépendants doivent être combinés, la solution habituelle consiste à écrire des fonctions qui enrichissent une classe et à les appliquer les unes après les autres.
Jusqu'où peut-on empiler les niveaux ?
Le langage n'impose aucune limite, l'expérience si. Au-delà de deux niveaux, comprendre d'où vient une méthode demande d'ouvrir trois fichiers, et une modification dans la classe racine se répercute sur des descendants qu'on ne voit plus. Deux niveaux couvrent la quasi-totalité des besoins réels.
Héritage ou composition ?
Posez-vous la question du lien : l'héritage dit qu'un objet est une variété d'un autre, la composition dit qu'un objet contient un autre et lui délègue du travail. Quand la réponse hésite, la composition vieillit mieux, parce qu'elle se remplace sans toucher au reste. Réservez extends aux familles de types réellement stables.