Prototypes et classes
Chaîne de prototypes, classes ES6, héritage, Object.* et bonnes pratiques this.
JavaScript est un langage prototype-based : chaque objet a un lien vers un autre
objet (son prototype) dont il hérite des propriétés. La syntaxe class (ES6)
est du sucre syntaxique au-dessus de ce mécanisme. Comprendre les prototypes
explique pourquoi instanceof, this et l'héritage fonctionnent comme ils le
font.
🔗 La chaîne de prototypes
Chaque objet JavaScript possède un lien interne vers un autre objet, accessible via
Object.getPrototypeOf(obj) (ou historiquement __proto__). Quand on accède à une
propriété manquante sur l'objet, JavaScript remonte la chaîne jusqu'à trouver la
propriété ou atteindre null.
const animal = { parler() { console.log("...") } }
const chien = Object.create(animal)
chien.race = "Labrador"
chien.race // "Labrador" — propre à chien
chien.parler() // "..." — hérité du prototype animal
chien.toString // hérité de Object.prototype (tout en bas de la chaîne)Visualizing la chaîne
chien ──> animal ──> Object.prototype ──> nullMéthodes utiles pour inspecter :
Object.getPrototypeOf(chien) === animal // true
chien instanceof Object // true (remonte jusqu'à Object.prototype)
Object.prototype.isPrototypeOf(chien) // trueConstructeur et .prototype
function Personne(nom) {
this.nom = nom
}
Personne.prototype.sePresenter = function() {
console.log(`Je m'appelle ${this.nom}`)
}
const alice = new Personne("Alice")
alice.sePresenter() // "Je m'appelle Alice"
alice instanceof Personne // trueLa propriété .prototype de la fonction constructeur est l'objet qui devient le
prototype de chaque instance créée avec new. Ce n'est pas le prototype de la
fonction elle-même (Personne.__proto__ est Function.prototype).
.prototype ≠ __proto__
Personne.prototype est le prototype des instances (alice.__proto__ === Personne.prototype). Personne.__proto__ est le prototype de la fonction elle-même
(Function.prototype). Confondre les deux est une source fréquente de bugs.
🏷️ Classes ES6
La syntaxe class est plus lisible mais fonctionne exactement sur le même mécanisme
prototype.
class Animal {
constructor(espece) {
this.espece = espece
}
identifier() {
return `Je suis un ${this.espece}`
}
// Méthode statique — appelée sur la classe, pas l'instance
static compter() {
return Animal.count++
}
}
class Chien extends Animal {
constructor(race) {
super("Chien") // appelle Animal constructor
this.race = race
}
identifier() {
return `${this.race} (${super.identifier()})`
}
}
const milou = new Chien("Terry")
milou.identifier() // "Terry (Je suis un Chien)"
Chien.compter() // appel statiquePoints clés à retenir
super()doit être appelé avantthisdans le constructeur d'une sous-classe- Les méthodes de classe sont ajoutées sur
Classname.prototype(pas sur l'instance) - Les méthodes
staticsont sur la classe elle-même (Classname.nom) - Les getters/setters fonctionnent avec
get nom()/set nom(val)
class User {
constructor(nom) {
this._nom = nom
}
get nom() {
return this._nom.trim()
}
set nom(valeur) {
if (!valeur) throw new Error("Nom vide")
this._nom = valeur
}
}Champs privés (ES2022+)
class Compte {
#solde = 0
crediter(montant) {
this.#solde += montant
}
getSolde() {
return this.#solde
}
}Les champs privés (#nom) sont réellement inaccessibles hors de la classe — plus
fort que la convention _nom (qui n'empêche que l'accès accidentel).
🧰 Méthodes utilitaires Object
Object expose de nombreuses méthodes pour inspecter et manipuler des objets sans
les modifier ou en créant de nouveaux.
Inspection
Object.keys(obj) // tableau des clés énumérables propres
Object.values(obj) // tableau des valeurs correspondantes
Object.entries(obj) // tableau de [clé, valeur]
Object.hasOwn(obj, key) // true si la clé est propre (sans remonter la chaîne)
obj.hasOwnProperty(key) // ancienne forme (peut être falsifié)const user = { nom: "Alice", age: 25 }
Object.entries(user)
// [["nom", "Alice"], ["age", 25]]Création et manipulation
Object.assign(cible, source1, source2) // copie les clés énumérables (shallow copy)
Object.create(proto) // nouvel objet avec prototype donné
Object.defineProperty(obj, key, descriptor) // contrôle fin : enumerable, writable, configurable
Object.freeze(obj) // empêche modification / ajout / suppression
Object.seal(obj) // empêche ajout / suppression, permet modification existanteassign est une copie shallow
Object.assign({}, obj) ne clone pas les objets imbriqués — il copie la
référence. { ...obj } fait la même chose. Pour un clone profond, utiliser
structuredClone(obj) (navigateur/Node moderne) ou une librairie dédiée.
Comparaison : Object.keys vs for…in
const parent = { heritage: true }
const enfant = Object.create(parent)
enfant.propre = "valeur"
Object.keys(enfant) // ["propre"] — uniquement les clés propres
for (const k in enfant) // "propre", "heritage" — remonte la chaîneUtiliser Object.keys() quand on veut uniquement les propriétés directes de l'objet.
Utiliser for…in (avec hasOwnProperty pour filtrer) si on veut inspecter la
totalité de la chaîne de prototypes.
for…in sans hasOwnProperty
for (const k in obj) inclut les propriétés héritées de la chaîne prototype.
Sans obj.hasOwnProperty(k), on risque de traiter des méthodes natives
(toString, valueOf…) comme des données. Préférer Object.keys() pour les données.
🛠️ Exercices pratiques
Classes et héritage — Animal, Chien, Object.keys
Comprendre la chaîne de prototypes et les classes ES6
Vous allez créer des classes, implémenter l'héritage avec extends et super, utiliser une méthode statique et comparer Object.keys vs for...in.
- Définir une classe avec constructeur et méthode
- Implémenter l'héritage avec
extendsetsuper - Ajouter une méthode statique avec compteur
- Comparer Object.keys vs for...in (propriétés propres vs héritées)
Partie A — Classe Animal
Créez une classe Animal avec :
- un constructeur
(espece)initialisantthis.espece - une méthode
identifier()qui retourne"Je suis un [espece]"
Indice — Constructeur et méthode
Le constructeur est défini avec constructor(espece) { this.espece = espece }. Les méthodes sont définies directement dans le corps de la classe (pas de function keyword).
Partie B — Héritage avec Chien extends Animal
Créez une classe Chien extends Animal avec :
- un constructeur
(race)qui appellesuper("Chien")et stockethis.race - une méthode
identifier()qui retourne"[race] — Je suis un Chien"
Indice — super()
super("Chien") doit être appelé AVANT d'utiliser this. La méthode identifier() peut appeler super.identifier() si elle veut réutiliser la version parente.
Partie C — Méthode statique compter()
Ajoutez une méthode statique compter() sur Chien :
- propriété statique
countincrémentée à chaque instanciation compter()retourne le nombre de chiens créés
Indice — Static
Déclarez static count = 0 dans le corps de la classe. Dans le constructeur, incrémentez Chien.count++. La méthode static compter() retourne Chien.count.
Partie D — Object.keys vs for…in
Créez un objet enfant avec 1 propriété propre et un prototype hérité. Vérifiez que Object.keys(enfant) ne retourne que la propriété propre, contrairement à for...in qui remonte la chaîne.
Indice — Object.create
Object.create(parent) crée un objet dont le prototype est parent. Ajoutez une propriété avec enfant.propre = "valeur". Object.keys() ne retourne que les propriétés propres ; for...in inclut les héritées.
Code à valider
🧩 Synthèse du bloc
Chaîne de prototypes
Chaque objet a un prototype ; l'accès à une propriété remonte la chaîne jusqu'à null. Object.create et new le contrôlent.
Classes ES6
Sucre syntaxique sur les prototypes. extends, super, static, getters/setters, champs privés #.
Object.*
keys/values/entries pour inspecter, assign pour copier (shallow), freeze/seal pour contrôler la mutabilité.
this et instanceof
this dépend de la façon dont la fonction est appelée. instanceof remonte la chaîne prototype pour tester.
Quiz de fin de bloc
Quiz du chapitre
JavaScript avancé
Terminez le quiz du chapitre pour le marquer comme complété.