Jusqu'ici, tu écrivais du code "de terrain". Des variables qui traînent, des fonctions qui agissent dans l'urgence. La POO (Programmation Orientée Objet), c'est une autre discipline : tu organises tout autour d'objets. Comme si tu créais des agents, chacun avec son dossier, ses méthodes, ses secrets.
Pourquoi passer à ça ?
- Caché : Tu crées un "modèle" (une classe) et tu le clones partout sans laisser de traces.
- Ordonné : Tout ce qui concerne un "Contact" est au même endroit.
- Robuste : Modifier un détail sans faire s'écrouler l'ensemble.
- Propre : Plus de variables orphelines qui flottent comme des témoins gênants.
En JavaScript, il y a toujours deux visages :
- L'ancien système (prototypes) : La mécanique réelle, sous le capot.
- La nouvelle syntaxe (classes ES6) : Une façade plus propre, mais qui utilise les mêmes engrenages.
On survole rapidement l'ancien, juste pour comprendre les traces. Puis on s'installe dans le moderne.
8.1 Le Système Prototype - Les Fondations Sous la Ville
Le Modèle Objet de JavaScript
// En JS, presque tout est un objet, même ce qui n'en a pas l'air.
let liste = [1, 2, 3];
console.log(typeof liste); // "object"
let fonction = function() {};
console.log(typeof fonction); // "function" (un objet spécial)
// Même les types primitifs ont des doublures objets
let phrase = "code rouge";
console.log(phrase.toUpperCase()); // "CODE ROUGE"
// Une string primitive qui a une méthode ? C'est le prototype qui agit.
Le Chaînage de Prototypes - Comment JS Trouve ce Qu'il Cherche
// Chaque objet a un lien vers son "prototype parent". Une chaîne invisible.
let source = {
méthodeSecrète: function() {
return "Message codé.";
}
};
let relais = {
signal: function() {
return "Signal émis.";
}
};
// On relie relais à source comme prototype
Object.setPrototypeOf(relais, source);
// Maintenant relais peut accéder aux méthodes de source.
console.log(relais.signal()); // "Signal émis." (sa propre méthode)
console.log(relais.méthodeSecrète()); // "Message codé." (trouvée dans le prototype)
// Le mécanisme : si JS ne trouve pas sur l'objet, il remonte la chaîne.
Object.create() - Créer un Objet Avec un Prototype Défini
// Plutôt que modifier après, on peut créer avec le bon prototype d'emblée.
let base = {
vérifier() {
return "Identité confirmée.";
}
};
let agent = Object.create(base); // agent a base comme prototype.
agent.action = function() { return "En mission." };
console.log(agent.action()); // "En mission."
console.log(agent.vérifier()); // "Identité confirmée." (héritée)
instanceof - Vérifier la Lignée
function Source() {}
let message = new Source();
console.log(message instanceof Source); // true
console.log(message instanceof Object); // true (tout hérite d'Object)
// Attention : instanceof vérifie la chaîne de prototypes, pas le type brut.
(Note : Les constructeurs de fonctions à l'ancienne et la manipulation directe de prototype sont des patterns historiques. Aujourd'hui, on utilise les classes. Mais savoir qu'ils existent permet de lire du code ancien.)
8.2 Classes ES6+ - La Façade Moderne
Déclaration de Classe
// Une syntaxe enfin lisible.
class Contact {
constructor(alias, niveau) {
this.alias = alias;
this.niveau = niveau;
this.missions = 0; // Propriété par défaut
}
// Méthode d'instance
envoyerMessage() {
return `${this.alias} transmet : situation sous contrôle.`;
}
terminerMission(difficulté) {
this.missions += difficulté;
return `Mission terminée. Score total : ${this.missions}`;
}
}
// Utilisation
let contactAlpha = new Contact("Alpha", 7);
console.log(contactAlpha.envoyerMessage()); // "Alpha transmet : situation sous contrôle."
console.log(contactAlpha.terminerMission(3)); // "Mission terminée. Score total : 3"
Héritage avec extends et super
// Une classe parent
class Agent {
constructor(codename) {
this.codename = codename;
}
rapporter() {
return `${this.codename} rapporte.`;
}
}
// Une classe enfant qui hérite
class Infiltré extends Agent {
constructor(codename, couverture) {
super(codename); // Appelle le constructeur parent
this.couverture = couverture;
}
écouter() {
return `${this.codename} (${this.couverture}) est à l'écoute.`;
}
// On peut redéfinir (overrider) une méthode parent
rapporter() {
return `${super.rapporter()} Canal sécurisé.`;
}
}
let shadow = new Infiltré("Shadow", "portier");
console.log(shadow.écouter()); // "Shadow (portier) est à l'écoute."
console.log(shadow.rapporter()); // "Shadow rapporte. Canal sécurisé."
// Vérification de la lignée
console.log(shadow instanceof Infiltré); // true
console.log(shadow instanceof Agent); // true
8.3 Propriétés et Méthodes de l'Ombre
Getters et Setters - Contrôler l'Accès
class Dossier {
constructor(nom, classification) {
this._nom = nom; // Convention : _ pour "interne"
this._classification = classification;
}
// Getter (accès comme une propriété)
get nom() {
return this._nom.toUpperCase();
}
// Setter (avec validation)
set classification(niveau) {
if (['confidentiel', 'secret', 'top secret'].includes(niveau)) {
this._classification = niveau;
} else {
console.log("Niveau de classification invalide.");
}
}
get classification() {
return this._classification;
}
// Propriété calculée
get estAccèsRestreint() {
return this._classification === 'top secret';
}
}
let dossierX = new Dossier("Projet Phoenix", "secret");
console.log(dossierX.nom); // "PROJET PHOENIX"
console.log(dossierX.estAccèsRestreint); // false
dossierX.classification = 'top secret'; // setter
dossierX.classification = 'public'; // "Niveau de classification invalide."
Méthodes et Propriétés Statiques - Pour la Classe, Pas l'Instance
class Chiffrement {
// Propriété statique (liée à la classe elle-même)
static ALGORITHME = "AES-256";
// Méthode statique (appelée sur la classe, pas une instance)
static générerClé() {
return Math.random().toString(36).substring(2, 15);
}
// Méthode d'instance (besoin d'un objet)
coder(message) {
return `[CODÉ:${message}]`;
}
}
// Appel des méthodes statiques (SANS `new`)
console.log(Chiffrement.ALGORITHME); // "AES-256"
console.log(Chiffrement.générerClé()); // "a7b9c3d" (exemple)
// Impossible d'appeler statique sur instance
let codeur = new Chiffrement();
console.log(codeur.coder("alerte")); // "[CODÉ:alerte]"
// codeur.générerClé(); // ❌ ERREUR !
Propriétés Privées (#) - Le Vrai Secret
class Coffre {
#contenu = ''; // Propriété privée (vraiment inaccessible de l'extérieur)
constructor(lieu) {
this.lieu = lieu; // Public
}
déposer(objet) {
this.#contenu = objet;
}
retirer() {
const objet = this.#contenu;
this.#contenu = '';
return objet;
}
// Getter pour vérifier (lecture seulement)
get vide() {
return this.#contenu === '';
}
}
let cache = new Coffre("ponton 7");
cache.déposer("microfilms");
console.log(cache.vide); // false
// Impossible d'accéder directement
// console.log(cache.#contenu); // ❌ ERREUR Syntaxique !
// cache.#contenu = "autre chose"; // ❌ IMPOSSIBLE !
Comprendre this - Le Dernier Témoin
class Témoin {
constructor(pseudo) {
this.pseudo = pseudo;
}
identifier() {
console.log(`Je suis ${this.pseudo}.`);
}
identifierPlusTard() {
// Piège classique : `this` est perdu dans le callback.
setTimeout(function() {
console.log(`Je suis ${this.pseudo}.`); // ❌ this = global
}, 1000);
}
// Solutions :
identifierAvecPrécaution() {
// 1. Capturer `this` dans une variable.
let moi = this;
setTimeout(function() {
console.log(`Je suis ${moi.pseudo}.`);
}, 1000);
}
identifierAvecFlèche() {
// 2. Fonction fléchée (garde le `this` du contexte parent).
setTimeout(() => {
console.log(`Je suis ${this.pseudo}.`); // ✅ this = instance
}, 1000);
}
}
let témoin7 = new Témoin("Sept");
témoin7.identifier(); // "Je suis Sept."
témoin7.identifierPlusTard(); // "Je suis undefined."
témoin7.identifierAvecFlèche(); // "Je suis Sept." (après 1s)
call, apply, bind - Forcer l'Identité
let cible1 = { ville: "Detroit" };
let cible2 = { ville: "Chicago" };
function localiser(période, risque) {
console.log(`${this.ville} - ${période} - Risque ${risque}`);
}
// 1. call() : appelle avec un `this` spécifique.
localiser.call(cible1, "nuit", "élevé"); // "Detroit - nuit - Risque élevé"
// 2. apply() : pareil mais arguments dans un tableau.
localiser.apply(cible2, ["jour", "moyen"]); // "Chicago - jour - Risque moyen"
// 3. bind() : crée une nouvelle fonction avec `this` figé.
let surveillerDetroit = localiser.bind(cible1);
surveillerDetroit("aube", "faible"); // "Detroit - aube - Risque faible"
Résumé de ce module :
- Prototypes : La mécanique réelle de l'héritage en JS. Utile pour comprendre les traces anciennes.
- Classes ES6 : La syntaxe moderne. Utilise-la pour tout ton nouveau code.
- Getters/setters : Pour valider ou masquer les données à l'accès.
- Statique vs Instance :
Chiffrement.générerClé()vscodeur.coder(). - Propriétés privées (
#) : Pour enterrer vraiment des informations. this: Le témoin volatil. Les fonctions fléchées sont ton alibi.call/apply/bind: Pour les rares cas où tu dois substituer une identité.
La POO, c'est comme monter une cellule dormante :
- Avant : Des agents libres, agissant dans le désordre.
- Après : Chaque agent a un rôle, un dossier, des contacts définis. Le chaos devient réseau.
Ça demande de penser différemment. Mais une fois le réseau en place, il est beaucoup plus simple à contrôler. Et à faire disparaître.