Hériter d'une classe pose vite une question pratique : comment ajouter quelque chose au comportement du parent sans recopier ce qu'il fait déjà. Redéfinir une méthode l'écrase entièrement, ce qui est rarement l'intention.
super est le mot-clé qui permet de compléter plutôt que de remplacer. C'est aussi le seul moyen de faire exister une instance dans une classe fille.
Définition
super désigne la classe parente à l'intérieur d'une classe déclarée avec extends. Il s'emploie de deux façons : suivi de parenthèses dans le constructeur, ou suivi d'un point et d'un nom de méthode partout ailleurs.
class Produit {
constructor(nom, prix) {
this.nom = nom;
this.prix = prix;
}
etiquette() { return this.nom + " : " + this.prix + " euros"; }
}
class ProduitSolde extends Produit {
constructor(nom, prix, remise) {
super(nom, prix); // avant toute utilisation de this
this.remise = remise;
}
etiquette() {
return super.etiquette() + " (-" + this.remise + " %)";
}
}
console.log(new ProduitSolde("Clavier", 79, 20).etiquette());
// Clavier : 79 euros (-20 %)La méthode etiquette de la fille réutilise celle du parent et lui ajoute une mention. Le jour où le format d'étiquette du parent change, la version soldée suit toute seule, ce qu'un copier-coller n'aurait jamais permis.
Ses emplois, et ce qu'ils font
| Écriture | Ce qu'elle fait |
|---|---|
| super(...) dans le constructeur | Exécute le constructeur du parent et fait exister this |
| super.methode() dans une méthode | Appelle la version du parent en gardant l'instance courante |
| super.methode() dans un membre static | Appelle la version statique de la classe parente |
La deuxième ligne cache un détail important : la méthode du parent s'exécute bien sur l'instance en cours, donc this y désigne l'objet réel.
L'ordre est imposé dans le constructeur
Une classe fille qui déclare son propre constructeur doit appeler super() avant de toucher à this. Ce n'est pas une convention de style, c'est une règle du langage.
class Produit {
constructor(nom) { this.nom = nom; }
}
class Cassee extends Produit {
constructor(nom) {
this.nom = nom; // trop tôt : l'objet n'existe pas encore
super(nom);
}
}
try {
new Cassee("Souris");
} catch (erreur) {
console.log(erreur.name); // ReferenceError
}La raison est mécanique : c'est l'appel au parent qui fabrique l'objet. Tant qu'il n'a pas eu lieu, il n'y a rien à remplir, et le moteur refuse la lecture plutôt que de laisser passer une valeur vide.
Le mot-clé n'existe que dans une classe et dans les méthodes abrégées d'un objet littéral. Écrit dans une function ordinaire, il produit une erreur de syntaxe, avant même la première exécution.
Questions fréquentes
Faut-il toujours appeler super dans le constructeur ?
Dès que la classe hérite d'une autre et déclare son propre constructeur, oui, l'appel est obligatoire. Si la fille n'écrit aucun constructeur, le langage en ajoute un implicitement, qui transmet les arguments reçus. C'est d'ailleurs une bonne raison de ne pas écrire de constructeur quand il ne ferait que répéter celui du parent.
Peut-on appeler une méthode du grand-parent ?
Pas directement : le mot-clé ne remonte que d'un cran. Sur trois niveaux, chaque classe appelle son parent immédiat et la chaîne se propage naturellement. Un besoin de sauter un niveau signale presque toujours une hiérarchie trop profonde, à revoir avant d'aller chercher une astuce.
Que vaut this juste avant l'appel ?
Rien du tout, et c'est précisément le sens de l'erreur obtenue plus haut. La variable existe mais reste dans une zone où toute lecture est refusée, jusqu'à l'exécution du constructeur parent. Une fois cet appel passé, la lecture redevient ordinaire pour tout le reste du constructeur de la fille.