Avancé30 min de lecture

Classes & Héritage

Apprends la syntaxe des classes ES6 pour créer des objets, utiliser les constructeurs, méthodes, getters/setters, membres statiques, et l'héritage avec extends.

Syntaxe des Classes

ES6 a introduit le mot-clé class comme une syntaxe plus claire pour créer des objets avec un comportement partagé. Une classe est essentiellement un plan pour créer des objets.

javascript
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
  }
}

const alice = new Person('Alice', 30);
console.log(alice.greet()); // "Hi, I'm Alice and I'm 30 years old."

Éléments clés :

  • constructor — une méthode spéciale appelée automatiquement lorsque tu crées une instance avec new. Elle initialise les propriétés de l'objet.
  • Méthodes d'instance — fonctions définies à l'intérieur de la classe qui sont disponibles sur chaque instance (par exemple, greet()).
  • Mot-clé new — crée une nouvelle instance de la classe.

Les classes sont du sucre syntaxique par-dessus l'héritage prototypal existant de JavaScript. En coulisses, class Person crée toujours une fonction constructeur et ajoute des méthodes à Person.prototype. Mais la syntaxe des classes est beaucoup plus lisible.

Contrairement aux déclarations de fonction, les déclarations de classe ne sont PAS remontées :

javascript
const p = new Person('Alice', 30); // ReferenceError!
class Person { ... }

Tu dois définir la classe avant de l'utiliser. C'est voulu — cela prévient les bugs subtils causés par la remontée.

Getters & Setters

Les getters et setters te permettent de définir des propriétés calculées et d'ajouter une validation lors de l'assignation de valeurs. Ils ressemblent à des méthodes mais sont accessibles comme des propriétés (sans parenthèses).

javascript
class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  // Getter — propriété calculée
  get area() {
    return Math.PI * this.radius ** 2;
  }

  get diameter() {
    return this.radius * 2;
  }

  // Setter — validation lors de l'assignation
  set radius(value) {
    if (value < 0) {
      throw new RangeError('Radius cannot be negative');
    }
    this._radius = value;
  }

  get radius() {
    return this._radius;
  }
}

const c = new Circle(5);
console.log(c.area);     // 78.539... (pas de parenthèses !)
console.log(c.diameter); // 10
c.radius = 10;           // utilise le setter
// c.radius = -1;        // lance RangeError

ES2022 a introduit les champs privés avec le préfixe #. Les champs privés sont vraiment inaccessibles depuis l'extérieur de la classe :

javascript
class BankAccount {
  #balance = 0; // champ privé

  deposit(amount) {
    this.#balance += amount;
  }

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

const account = new BankAccount();
account.deposit(100);
console.log(account.balance);  // 100
// console.log(account.#balance); // SyntaxError!

Méthodes & Propriétés Statiques

Les méthodes et propriétés statiques appartiennent à la classe elle-même, pas aux instances. Elles sont appelées sur la classe, pas sur les objets créés à partir de celle-ci.

javascript
class MathUtils {
  static PI = 3.14159;

  static square(n) {
    return n * n;
  }

  static cube(n) {
    return n * n * n;
  }
}

console.log(MathUtils.PI);        // 3.14159
console.log(MathUtils.square(4)); // 16
console.log(MathUtils.cube(3));   // 27

// Ne peut pas être appelé sur les instances :
const m = new MathUtils();
// m.square(4); // TypeError: m.square is not a function

Les méthodes statiques sont couramment utilisées pour :

  • Fonctions utilitaires qui n'ont pas besoin de données d'instance
  • Méthodes factory qui créent des instances de manières spécifiques :
    javascript
    class User {
      constructor(name, role) {
        this.name = name;
        this.role = role;
      }
    
      static createAdmin(name) {
        return new User(name, 'admin');
      }
    
      static createGuest() {
        return new User('Guest', 'guest');
      }
    }
    
    const admin = User.createAdmin('Alice');
    const guest = User.createGuest();

Tu as déjà utilisé des méthodes statiques intégrées : Array.isArray(), Object.keys(), Date.now(), JSON.parse(). Elles appartiennent toutes à la classe, pas aux instances.

Héritage avec extends

Le mot-clé extends crée une classe enfant qui hérite de toutes les propriétés et méthodes d'une classe parente :

javascript
class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  speak() {
    return `${this.name} says ${this.sound}!`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name, 'Woof'); // appelle le constructeur parent
    this.tricks = [];
  }

  learn(trick) {
    this.tricks.push(trick);
  }

  showTricks() {
    return `${this.name} knows: ${this.tricks.join(', ')}`;
  }
}

const rex = new Dog('Rex');
console.log(rex.speak());      // 'Rex says Woof!' (hérité)
rex.learn('sit');
rex.learn('shake');
console.log(rex.showTricks()); // 'Rex knows: sit, shake'

Règles clés pour l'héritage :

  1. super() doit être appelé dans le constructeur enfant avant d'utiliser this. Il appelle le constructeur du parent.
  2. Les classes enfant peuvent surcharger les méthodes parentes en définissant une méthode avec le même nom.
  3. Utilise super.method() pour appeler la version parente d'une méthode surchargée :
    javascript
    class Cat extends Animal {
      speak() {
        return super.speak() + ' (purrs)';
      }
    }
  4. L'opérateur instanceof vérifie la chaîne d'héritage :
    javascript
    console.log(rex instanceof Dog);    // true
    console.log(rex instanceof Animal); // true

L'héritage crée une relation "est-un". Un Dog est un Animal. Utilise-le quand il y a une hiérarchie claire, mais préfère la composition (objets contenant d'autres objets) quand la relation est "a-un".

Classes en Action

html
<div id="output"></div>

<script>
  class Animal {
    constructor(name, sound) {
      this.name = name;
      this.sound = sound;
    }

    speak() {
      return `${this.name} says ${this.sound}!`;
    }
  }

  class Dog extends Animal {
    constructor(name) {
      super(name, 'Woof');
      this.tricks = [];
    }

    learn(trick) {
      this.tricks.push(trick);
    }

    describe() {
      const base = this.speak();
      const tricks = this.tricks.length
        ? ` Knows: ${this.tricks.join(', ')}`
        : ' No tricks yet.';
      return base + tricks;
    }
  }

  const dog = new Dog('Buddy');
  dog.learn('sit');
  dog.learn('roll over');

  const cat = new Animal('Whiskers', 'Meow');

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>${dog.describe()}</p>
    <p>${cat.speak()}</p>
    <p>Is Dog an Animal? ${dog instanceof Animal}</p>
  `;
</script>

Que dois-tu appeler dans le constructeur d'une classe enfant avant d'utiliser `this` ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.