frontend / javascript
Classes ES6
Explication
Ce que vous allez apprendre
- Écrire une classe avec constructeur, méthodes et propriétés
- Mettre en place un héritage avec
extendsetsuper() - Expliquer pourquoi
super()doit être appelé avant tout accès àthis - Déclarer un champ réellement privé avec
#champ, et pourquoi c'est différent de_champ - Composer plusieurs comportements avec le pattern des mixins, sans héritage multiple
Dans quel contexte ?
Une équipe backend Node.js découvre qu'un champ censé être privé, _solde sur une classe CompteBancaire, est modifié directement depuis un autre module avec compte._solde = 999999, contournant toute la logique de validation du dépôt/retrait. La convention _ n'a jamais été qu'une convention de nommage : rien ne l'empêche techniquement d'être lue ou modifiée de l'extérieur. La correction consiste à migrer vers un vrai champ privé #solde, qui lève une erreur de syntaxe si on tente d'y accéder hors de la classe.
1. Une nouvelle syntaxe, pas un nouveau mécanisme
Les classes ES6 n'introduisent aucun nouveau paradigme en JavaScript : elles habillent le système de prototypes vu à la leçon précédente d'une syntaxe plus proche de ce qu'on trouve en Java ou en Python. L'objectif est la lisibilité : regrouper au même endroit un constructeur, des méthodes et des propriétés.
Prérequis
Cette leçon s'appuie directement sur la leçon précédente sur les prototypes : une classe JavaScript reste, sous le capot, une fonction constructeur avec un prototype. Si la notion de chaîne de prototypes n'est pas claire, relisez-la avant de continuer.
2. Hériter avec extends et super
Le mot-clé extends met en place l'héritage entre classes. super() permet à une classe fille d'appeler le constructeur de sa classe parente — une étape obligatoire avant de pouvoir utiliser this dans le constructeur enfant, sous peine d'erreur.
3. Pourquoi super() est réellement nécessaire
Ce n'est pas un simple détail de syntaxe : super() initialise réellement l'objet parent avant que la classe enfant ne puisse y ajouter ses propres champs. Sans cet appel, l'objet enfant n'existerait tout simplement pas encore.
4. La vraie nouveauté : les champs privés
Une fonctionnalité mérite une attention particulière : les champs privés, notés avec un # devant leur nom. Contrairement à la convention historique _champ (qui reste techniquement accessible depuis l'extérieur), un champ #champ est réellement inaccessible en dehors de la classe.
| Notation | Accessible depuis l'extérieur ? | Nature |
|---|---|---|
champ | Oui | Propriété publique normale |
_champ | Oui (juste une convention) | Propriété publique "à ne pas toucher" |
#champ | Non (erreur de syntaxe) | Vraie propriété privée, imposée par le moteur |
Piège fréquent
Croire que _champ protège une donnée est une erreur classique de code review : rien n'empêche instance._champ = "n'importe quoi" depuis n'importe où. Seul #champ offre une vraie garantie, vérifiée par le moteur JavaScript lui-même.
5. Composer sans hiérarchie rigide
JavaScript ne supporte pas l'héritage multiple nativement, mais le pattern des "mixins" — des fonctions qui retournent une classe étendue — permet de composer plusieurs comportements sans construire une hiérarchie rigide.
Cette leçon boucle la compréhension de la programmation orientée objet en JS, amorcée avec les prototypes à la leçon précédente.
Commandes & code
Classes ES6
La syntaxe moderne pour structurer la programmation orientée objet en JS.
class CompteBancaire {
// Champs de classe (class fields, standard depuis ES2022)
#solde; // champ prive : accessible SEULEMENT depuis l'interieur de la classe
static tauxInteret = 0.02; // propriete statique, partagee par la classe
constructor(titulaire, soldeInitial = 0) {
this.titulaire = titulaire;
this.#solde = soldeInitial;
}
deposer(montant) {
if (montant <= 0) throw new Error("Montant invalide");
this.#solde += montant;
return this; // permet le chainage de methodes
}
retirer(montant) {
if (montant > this.#solde) throw new Error("Solde insuffisant");
this.#solde -= montant;
return this;
}
get solde() { // getter : accessible comme une propriete
return this.#solde;
}
static creerVide(titulaire) { // methode statique : constructeur alternatif
return new CompteBancaire(titulaire, 0);
}
toString() {
return `Compte de ${this.titulaire} : ${this.#solde} EUR`;
}
}
const compte = new CompteBancaire("Alice", 1000);
compte.deposer(500).retirer(200); // chainage grace au "return this"
console.log(compte.solde); // 1300
console.log(`${compte}`); // utilise toString()
// console.log(compte.#solde); // SyntaxError hors de la classe : vraie encapsulation
// Heritage avec extends / super
class CompteEpargne extends CompteBancaire {
#tauxBonus;
constructor(titulaire, soldeInitial, tauxBonus) {
super(titulaire, soldeInitial); // DOIT etre appele avant tout usage de "this"
this.#tauxBonus = tauxBonus;
}
appliquerInterets() {
const interets = this.solde * (CompteBancaire.tauxInteret + this.#tauxBonus);
this.deposer(interets);
return interets;
}
}
const epargne = new CompteEpargne("Bob", 1000, 0.01);
epargne.appliquerInterets();
console.log(epargne.solde);
// Polymorphisme : override d'une methode
class CompteVIP extends CompteBancaire {
retirer(montant) {
console.log("Retrait VIP : pas de limite de decouvert");
this.deposer(0); // exemple simplifie
return this;
}
}
// Methodes et champs statiques prives
class CompteurInstances {
static #nombre = 0;
constructor() {
CompteurInstances.#nombre++;
}
static get total() {
return CompteurInstances.#nombre;
}
}
new CompteurInstances();
new CompteurInstances();
console.log(CompteurInstances.total); // 2
// Static initialization blocks (ES2022) : logique d'init complexe au niveau classe
class Configuration {
static valeurs;
static {
// s'execute une seule fois au chargement de la classe
Configuration.valeurs = { env: "production", version: "1.0" };
}
}
// instanceof et verification de type avec les classes
console.log(epargne instanceof CompteBancaire); // true
console.log(epargne instanceof CompteEpargne); // true
// Mixins : simuler l'heritage multiple (JS ne le supporte pas nativement)
const Horodatable = (Base) => class extends Base {
constructor(...args) {
super(...args);
this.creeLe = new Date();
}
};
class CompteHorodate extends Horodatable(CompteBancaire) {}
const ch = new CompteHorodate("Cy", 500);
console.log(ch.creeLe instanceof Date); // trueRésumé
- Les champs privés (
#champ) offrent une vraie encapsulation, contrairement à la convention_champ. super()doit être appelé avant tout usage dethisdans le constructeur d'une sous-classe.- Les mixins simulent l'héritage multiple via des fonctions qui retournent des classes étendues.
- Les blocs d'initialisation statique (
static { }, ES2022) exécutent une logique complexe au chargement.
Exercices pratiques
Mission : sécuriser le compte bancaire VIP
Objectif : Corriger une fausse encapsulation avec convention `_champ`, implémenter un héritage correct avec super(), et garantir un vrai champ privé.
Contexte
Un développeur junior de Technologik a écrit cette classe pour un compte bancaire, en pensant que le solde était protégé :
class CompteBancaire {
constructor(titulaire, solde) {
this.titulaire = titulaire;
this._solde = solde;
}
}
const c = new CompteBancaire("Alice", 1000);
c._solde = -99999; // personne n'empêche ça !Un audit de sécurité interne signale que n'importe quel code peut modifier _solde directement, en contournant toute validation. Il faut aussi créer une classe CompteVIP qui hérite de CompteBancaire et ajoute une méthode retirer sans plafond, tout en restant valide.