Adloun

Classes et modules

Cours · JavaScript : rendre le Web interactif (école d'ingénieur), chapitre 6 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun

Au chapitre précédent, un livre était un objet littéral, écrit à la main. Pour la Librairie du Campus, cela suffit tant qu'il s'agit de données. Mais le panier, lui, a un comportement : on y ajoute un livre, on en retire un, il calcule son total, il refuse une quantité nulle, et personne ne doit pouvoir modifier ses lignes sans passer par lui. C'est exactement ce que fait une classe en Java : regrouper un état et les méthodes qui le protègent.

JavaScript a des classes depuis 2015, avec une syntaxe volontairement proche de celle de Java : class, constructor, extends, super, static, des champs privés. Mais sous cette syntaxe familière, le mécanisme est différent : il n'y a pas de types, pas de surcharge, et l'héritage passe par des prototypes. Ce chapitre présente les classes, ce qu'elles cachent, et les modules, qui permettent de ranger chaque classe dans son fichier, comme en Java chaque classe publique a le sien.

Objectifs du chapitre

  • Écrire une classe : constructeur, méthodes, champs publics et privés, accesseurs, membres statiques.
  • Hériter avec extends et super ; redéfinir une méthode ; tester avec instanceof.
  • Comprendre ce qu'une classe produit réellement : une fonction et un prototype partagé.
  • Éviter de perdre this en passant une méthode comme rappel.
  • Découper un programme en modules : export, import, export par défaut.

6.1 De l'objet littéral à la classe

Pour fabriquer plusieurs livres de même forme, on peut écrire une fonction qui rend un objet littéral, une fabrique :


function creerLivre(titre, prix) {
  return {
    titre,
    prix,
    etiquette() {
      return `${this.titre} — ${this.prix} €`;
    },
  };
}

const a = creerLivre("Analyse MPSI", 45);
const b = creerLivre("Le Petit Prince", 8.5);
console.log(a.etiquette());
console.log(a.etiquette === b.etiquette);

Analyse MPSI — 45 €
false

Cela fonctionne, mais chaque appel crée une nouvelle fonction etiquette : cent livres, cent copies de la même méthode (d'où le false). La classe résout ce problème et donne un nom au type :


class Livre {
  constructor(titre, prix) {
    this.titre = titre;
    this.prix = prix;
  }

  etiquette() {
    return `${this.titre} — ${this.prix} €`;
  }

  remise(pourcentage) {
    return new Livre(this.titre, Math.round(this.prix * (100 - pourcentage)) / 100);
  }
}

const a = new Livre("Analyse MPSI", 45);
const b = new Livre("Le Petit Prince", 8.5);
console.log(a.etiquette());
console.log(a.remise(10).etiquette());
console.log(a.etiquette === b.etiquette);
console.log(a instanceof Livre, typeof Livre);
console.log(a);

Analyse MPSI — 45 €
Analyse MPSI — 40.5 €
true
true function
Livre { titre: 'Analyse MPSI', prix: 45 }
Définition 6.1Classe

Une déclaration class Nom { ... } définit un constructeur, la méthode constructor, appelée par new Nom(...) sur un objet neuf que this désigne, et des méthodes, partagées par tous les objets créés. objet instanceof Nom teste si un objet a été créé par cette classe (ou une sous-classe).

Les différences avec Java sont importantes :


class Livre {
  constructor(titre) {
    this.titre = titre;
  }
}

const l = Livre("Analyse MPSI");
console.log(l);

Uncaught TypeError: Class constructor Livre cannot be invoked without 'new'

6.2 Champs privés, accesseurs, membres statiques

6.2.1 Les champs privés

Une propriété ordinaire est publique : n'importe quel code peut la lire et la modifier. Depuis 2022, un nom commençant par # déclare un champ privé, inaccessible hors de la classe. Ce n'est pas une convention : le moteur l'interdit.


class Stock {
  #quantite;
  static #nombreDeStocks = 0;

  constructor(quantite) {
    if (!Number.isInteger(quantite) || quantite < 0) {
      throw new RangeError(`Quantité invalide : ${quantite}`);
    }
    this.#quantite = quantite;
    Stock.#nombreDeStocks += 1;
  }

  get quantite() {
    return this.#quantite;
  }

  get epuise() {
    return this.#quantite === 0;
  }

  retirer(n) {
    if (n > this.#quantite) {
      throw new RangeError(`Stock insuffisant : ${this.#quantite} < ${n}`);
    }
    this.#quantite -= n;
  }

  static get nombre() {
    return Stock.#nombreDeStocks;
  }
}

const java = new Stock(12);
java.retirer(3);
console.log(java.quantite, java.epuise);
try {
  java.quantite = 100;
} catch (erreur) {
  console.log(erreur.message);
}
console.log(Object.keys(java), JSON.stringify(java));
new Stock(0);
console.log(Stock.nombre);

try {
  java.retirer(50);
} catch (erreur) {
  console.log(erreur.name, "-", erreur.message);
}

9 false
Cannot set property quantite of #<Stock> which has only a getter
[] {}
2
RangeError - Stock insuffisant : 9 < 50

La suite du cours est sur Adloun

Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.

Lire la suite du cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →