Séance 11 — Les objets et les classes

Regrouper ce qui va ensemble

  • Créer un objet et lire ses propriétés
  • Parcourir un tableau d’objets
  • Fabriquer des objets en série avec une classe

1. Le problème

À la fin de la séance 10, on stockait les inscrits comme ça :

const inscrits = ["Camille, 19 ans — L2 Informatique"];

Une phrase. Impossible de trier par âge, de compter les L2, de changer le format d’affichage. L’information est là, mais elle est en bouillie.

Ce qu’on voudrait, c’est garder les morceaux séparés mais ensemble.

2. L’objet

const etudiant = {
  prenom: "Camille",
  age: 19,
  formation: "L2 Informatique",
  boursier: true,
};
{
  prenom  :  "Camille" ,
  └──┬──┘    └───┬───┘ │
     │           │     └── virgule entre chaque paire
     │           └──────── la VALEUR (n'importe quel type)
     └──────────────────── la PROPRIÉTÉ (le nom de l'information)
}
Structure Sert à Repère
Tableau [ ] Plusieurs choses du même genre Accès par position : t[0]
Objet { } Plusieurs informations sur une même chose Accès par nom : o.prenom

Lire et modifier

console.log(etudiant.prenom); // "Camille"
console.log(etudiant.age);    // 19

etudiant.age = 20;            // modifier
etudiant.ville = "Palaiseau"; // ajouter une propriété
AvertissementLes 3 erreurs classiques
etudiant.Prenom      // ❌ undefined — majuscule : ce n'est pas la même propriété
etudiant["prenom"]   // ✅ ça marche, mais on préfère le point
etudiant.prenom()    // ❌ erreur — ce n'est pas une fonction, pas de parenthèses

Quand vous obtenez undefined sur un objet, la cause est presque toujours une faute de frappe dans le nom de la propriété. JavaScript ne prévient pas : il répond simplement « je n’ai rien à ce nom-là ».

Exercice 11.1

Créez un objet livre avec un titre, un auteur, une année et un booléen lu. Affichez une phrase de présentation avec un gabarit.

app.js
const livre = {
  titre: "Le Petit Prince",
  auteur: "Antoine de Saint-Exupéry",
  annee: 1943,
  lu: true,
};

console.log(`${livre.titre}, de ${livre.auteur} (${livre.annee})`);

3. Un tableau d’objets

C’est la structure la plus utilisée du développement web. Retenez-la bien.

const etudiants = [
  { prenom: "Camille", age: 19, formation: "L2 Info" },
  { prenom: "Alex", age: 21, formation: "L3 Physique" },
  { prenom: "Sam", age: 18, formation: "L1 Sciences" },
];

On la parcourt avec la boucle de la séance 8 :

for (const etudiant of etudiants) {
  console.log(`${etudiant.prenom} a ${etudiant.age} ans`);
}

Étape 1 — Afficher un tableau d’objets dans la page

seance11.html
<h1>Les étudiants</h1>
<ul id="liste"></ul>
<p id="stats"></p>
app.js
const etudiants = [
  { prenom: "Camille", age: 19, formation: "L2 Info" },
  { prenom: "Alex", age: 21, formation: "L3 Physique" },
  { prenom: "Sam", age: 18, formation: "L1 Sciences" },
];

const liste = document.querySelector("#liste");
const stats = document.querySelector("#stats");

function afficher() {
  liste.innerHTML = "";
  let totalAge = 0;

  for (const etudiant of etudiants) {
    const li = document.createElement("li");
    li.textContent = `${etudiant.prenom} (${etudiant.age} ans) — ${etudiant.formation}`;
    liste.appendChild(li);

    totalAge = totalAge + etudiant.age;
  }

  const moyenne = totalAge / etudiants.length;
  stats.textContent = `${etudiants.length} étudiants, âge moyen ${moyenne.toFixed(1)} ans`;
}

afficher();
AstuceMaintenant, tout devient possible

Parce que les informations sont séparées, on peut :

// filtrer
for (const e of etudiants) {
  if (e.age >= 20) { … }
}

// trier par âge
etudiants.sort((a, b) => a.age - b.age);

// changer le format d'affichage sans toucher aux données
li.textContent = `${e.formation} : ${e.prenom}`;

Avec la version « phrase » de la séance 10, aucun de ces trois exemples n’était possible.

Exercice 11.2

À partir de ce tableau :

const produits = [
  { nom: "Clavier", prix: 25, stock: 4 },
  { nom: "Souris", prix: 12, stock: 0 },
  { nom: "Écran", prix: 150, stock: 2 },
];

Affichez dans la page une liste de la forme :

Clavier — 25 € (en stock)
Souris — 12 € (rupture)
Écran — 150 € (en stock)

Puis, en dessous : Valeur totale du stock : X € (prix × stock, additionné).

app.js
const produits = [
  { nom: "Clavier", prix: 25, stock: 4 },
  { nom: "Souris", prix: 12, stock: 0 },
  { nom: "Écran", prix: 150, stock: 2 },
];

const liste = document.querySelector("#liste");
const total = document.querySelector("#total");

let valeur = 0;
liste.innerHTML = "";

for (const produit of produits) {
  const li = document.createElement("li");

  if (produit.stock > 0) {
    li.textContent = `${produit.nom} — ${produit.prix} € (en stock)`;
  } else {
    li.textContent = `${produit.nom} — ${produit.prix} € (rupture)`;
  }

  liste.appendChild(li);
  valeur = valeur + produit.prix * produit.stock;
}

total.textContent = `Valeur totale du stock : ${valeur} €`;

Vérification : 25×4 + 12×0 + 150×2 = 100 + 0 + 300 = 400 €.

4. Des objets qui savent faire des choses

Une propriété peut contenir une fonction. On l’appelle alors une méthode.

const compte = {
  titulaire: "Camille",
  solde: 100,

  afficher() {
    return `${this.titulaire} : ${this.solde} €`;
  },
};

console.log(compte.afficher()); // Camille : 100 €
Importantthis = « l’objet dans lequel je suis »

À l’intérieur d’une méthode, this désigne l’objet lui-même.

this.solde veut dire « le solde de ce compte-ci ». Sans this, JavaScript chercherait une variable solde à l’extérieur, et ne la trouverait pas.

Dans une méthode, on écrit toujours this. devant les propriétés de l’objet.

5. La classe : un moule à objets

Écrire dix objets « compte bancaire » à la main serait pénible et source d’erreurs. Une classe est un moule : on la définit une fois, et on fabrique autant d’objets qu’on veut.

class CompteBancaire {
  constructor(titulaire, soldeInitial) {
    this.titulaire = titulaire;
    this.solde = soldeInitial;
  }

  deposer(montant) {
    this.solde = this.solde + montant;
  }

  retirer(montant) {
    if (montant > this.solde) {
      return false; // opération refusée
    }
    this.solde = this.solde - montant;
    return true;
  }

  afficher() {
    return `${this.titulaire} : ${this.solde} €`;
  }
}

On s’en sert avec new :

const compteA = new CompteBancaire("Camille", 100);
const compteB = new CompteBancaire("Alex", 50);

compteA.deposer(20);
console.log(compteA.afficher()); // Camille : 120 €
console.log(compteB.afficher()); // Alex : 50 €
ImportantLes 4 mots à comprendre
Mot Sens
class On définit un moule. Aucun objet n’existe encore
constructor La méthode exécutée automatiquement à la création. Elle remplit l’objet
this L’objet en train d’être fabriqué ou utilisé
new « Fabrique-moi un objet avec ce moule »

Image : la classe est le plan de la maison, new construit une maison réelle. On peut construire dix maisons avec le même plan — elles seront indépendantes.

AvertissementLes 3 erreurs à connaître
const c = CompteBancaire("Camille", 100);  // ❌ « new » oublié
const c = new CompteBancaire();            // ❌ arguments oubliés → undefined partout

class Compte {
  constructor(titulaire) {
    titulaire = titulaire;                 // ❌ « this. » oublié :
  }                                        //    ne range rien dans l'objet
}

L’oubli de this. est le plus vicieux : aucune erreur, mais l’objet reste vide.

Étape 2 — Une classe en action dans la page

banque.html
<h1>Ma banque</h1>
<p id="solde"></p>
<button id="depot">Déposer 10 €</button>
<button id="retrait">Retirer 10 €</button>
<p id="message"></p>
banque.js
class CompteBancaire {
  constructor(titulaire, soldeInitial) {
    this.titulaire = titulaire;
    this.solde = soldeInitial;
  }

  deposer(montant) {
    this.solde = this.solde + montant;
  }

  retirer(montant) {
    if (montant > this.solde) {
      return false;
    }
    this.solde = this.solde - montant;
    return true;
  }
}

const compte = new CompteBancaire("Camille", 50);

const soldeEl = document.querySelector("#solde");
const messageEl = document.querySelector("#message");

function afficher() {
  soldeEl.textContent = `${compte.titulaire} : ${compte.solde} €`;
}

document.querySelector("#depot").addEventListener("click", () => {
  compte.deposer(10);
  messageEl.textContent = "";
  afficher();
});

document.querySelector("#retrait").addEventListener("click", () => {
  const reussi = compte.retirer(10);
  if (!reussi) {
    messageEl.textContent = "Solde insuffisant.";
  } else {
    messageEl.textContent = "";
  }
  afficher();
});

afficher();
AstucePourquoi c’est mieux qu’une simple variable solde

Toutes les règles du compte (« on ne peut pas retirer plus que le solde ») sont à l’intérieur de la classe.

Quelqu’un qui utilise CompteBancaire ne peut pas se tromper : il appelle retirer() et la règle s’applique. Si on avait une simple variable let solde, chaque bout de code qui la modifie devrait re-vérifier la règle — et un jour, quelqu’un oublierait.

Une classe range ensemble les données et les règles qui les protègent. C’est ça, la programmation orientée objet.

Vous entendrez souvent parler de TypeScript. C’est du JavaScript auquel on ajoute des étiquettes de type :

class CompteBancaire {
  titulaire: string;
  solde: number;

  constructor(titulaire: string, soldeInitial: number) { … }

  deposer(montant: number): void { … }
}

L’intérêt : l’éditeur vous prévient avant même d’exécuter si vous écrivez compte.deposer("dix"). Sur un gros projet, ça évite énormément de bugs.

Ce n’est pas au programme de ce cours — mais maintenant, quand vous verrez : string dans du code, vous saurez ce que c’est. L’annexe Astro montre un exemple complet.

6. À toi de jouer

Exercice 11.3 — Prédire

class Chat {
  constructor(nom) {
    this.nom = nom;
    this.faim = 3;
  }

  manger() {
    this.faim = this.faim - 1;
  }
}

const a = new Chat("Moustache");
const b = new Chat("Ficelle");

a.manger();
a.manger();

console.log(a.nom, a.faim);
console.log(b.nom, b.faim);
Moustache 1
Ficelle 3

Le point essentiel : a et b sont deux objets indépendants. Nourrir a ne change rien à b, même s’ils sortent du même moule.

C’est toute la puissance des classes : un plan, autant d’exemplaires indépendants qu’on veut.

Exercice 11.4 — La classe Etudiant

Écrivez une classe Etudiant avec :

  • un constructor(prenom, notes) où notes est un tableau de nombres
  • une méthode moyenne() qui renvoie la moyenne
  • une méthode estRecu() qui renvoie true si la moyenne est ≥ 10
  • une méthode bulletin() qui renvoie Camille : moyenne 12.5 — reçu (ou — ajourné)

Testez avec deux étudiants.

app.js
class Etudiant {
  constructor(prenom, notes) {
    this.prenom = prenom;
    this.notes = notes;
  }

  moyenne() {
    let total = 0;
    for (const note of this.notes) {
      total = total + note;
    }
    return total / this.notes.length;
  }

  estRecu() {
    return this.moyenne() >= 10;
  }

  bulletin() {
    const resultat = this.estRecu() ? "reçu" : "ajourné";
    return `${this.prenom} : moyenne ${this.moyenne().toFixed(1)} — ${resultat}`;
  }
}

const a = new Etudiant("Camille", [12, 15, 10]);
const b = new Etudiant("Alex", [8, 7, 9]);

console.log(a.bulletin()); // Camille : moyenne 12.3 — reçu
console.log(b.bulletin()); // Alex : moyenne 8.0 — ajourné

Trois points à remarquer :

  1. Dans la boucle, on écrit this.notes, pas notes : le tableau appartient à l’objet.
  2. estRecu() réutilise moyenne(). Une méthode peut en appeler une autre : toujours avec this..
  3. condition ? "a" : "b" est un raccourci de if/else qui renvoie une valeur. Si ça vous perturbe, écrivez un if/else classique — c’est tout aussi correct.

Exercice 11.5 — Le répertoire de contacts 📇

Faites une page contacts.html qui combine tout ce qu’on sait :

  1. Une classe Contact avec nom, email, telephone et une méthode resume() qui renvoie Camille — camille@exemple.fr
  2. Un formulaire (3 champs) pour ajouter un contact
  3. Une validation : nom non vide, email contenant un @
  4. Le stockage dans un tableau de Contact
  5. L’affichage de la liste après chaque ajout, avec createElement
  6. Un compteur : 3 contacts enregistrés
contacts.html
<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <title>Répertoire</title>
    <link rel="stylesheet" href="contacts.css" />
  </head>
  <body>
    <h1>Mon répertoire</h1>

    <form id="formulaire">
      <p>
        <label for="nom">Nom :</label>
        <input type="text" id="nom" />
      </p>
      <p>
        <label for="email">Email :</label>
        <input type="email" id="email" />
      </p>
      <p>
        <label for="tel">Téléphone :</label>
        <input type="text" id="tel" />
      </p>
      <button type="submit">Ajouter</button>
    </form>

    <p id="erreur" class="erreur"></p>

    <ul id="liste"></ul>
    <p id="compteur"></p>

    <script src="contacts.js"></script>
  </body>
</html>
contacts.js
class Contact {
  constructor(nom, email, telephone) {
    this.nom = nom;
    this.email = email;
    this.telephone = telephone;
  }

  resume() {
    return `${this.nom} — ${this.email}`;
  }
}

const formulaire = document.querySelector("#formulaire");
const nomEl = document.querySelector("#nom");
const emailEl = document.querySelector("#email");
const telEl = document.querySelector("#tel");
const erreur = document.querySelector("#erreur");
const liste = document.querySelector("#liste");
const compteur = document.querySelector("#compteur");

const contacts = [];

function afficher() {
  liste.innerHTML = "";

  for (const contact of contacts) {
    const li = document.createElement("li");
    li.textContent = contact.resume();
    liste.appendChild(li);
  }

  compteur.textContent = `${contacts.length} contact(s) enregistré(s)`;
}

formulaire.addEventListener("submit", (event) => {
  event.preventDefault();

  const nom = nomEl.value.trim();
  const email = emailEl.value.trim();
  const tel = telEl.value.trim();

  if (nom === "") {
    erreur.textContent = "Le nom est obligatoire.";
    return;
  }

  if (!email.includes("@")) {
    erreur.textContent = "L'email doit contenir un @.";
    return;
  }

  erreur.textContent = "";
  contacts.push(new Contact(nom, email, tel));
  formulaire.reset();
  afficher();
});

afficher();

L’architecture à retenir, et qui ne changera plus :

classe Contact          → décrit UN contact et ce qu'il sait faire
const contacts = []     → les DONNÉES
afficher()              → dessine la page à partir des données
écouteur submit         → vérifie, modifie les données, redessine

Chaque partie a un rôle unique. C’est exactement comme ça que sont écrites les vraies applications web.

7. Fiche mémo

Un objet

const p = { prenom: "Camille", age: 19 };

p.prenom          // lire
p.age = 20;       // modifier
p.ville = "Lyon"; // ajouter

Un tableau d’objets

const gens = [
  { prenom: "Camille", age: 19 },
  { prenom: "Alex", age: 21 },
];

for (const g of gens) {
  console.log(g.prenom);
}

Une classe

class Truc {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }

  faire() {
    return this.a + this.b;
  }
}

const t = new Truc(1, 2);
t.faire(); // 3

Les 4 pièges

  1. new oublié
  2. this. oublié dans le constructeur
  3. Majuscule dans le nom d’une propriété → undefined
  4. Une méthode s’appelle avec parenthèses : t.faire()

8. Avant la prochaine séance