this dépend de QUI appelle
En JavaScript, this n'est pas figé : sa valeur dépend de comment la fonction est appelée, pas d'où elle est écrite. Dans une méthode appelée normalement (objet.methode()), this est l'objet. Mais si tu détaches la méthode (la passes en callback), this se perd.
class Compteur {
valeur = 0;
incr() { this.valeur++; console.log(this.valeur); }
}
const c = new Compteur();
c.incr(); // 1 → this = c, OK
const f = c.incr; // on DÉTACHE la méthode
// f(); // ❌ this est undefined → erreur
setTimeout(c.incr, 100); // ❌ même problème : appelé "tout seul"
C'est LE piège que tout débutant rencontre, surtout avec les addEventListener et les setTimeout.
La solution : les arrow functions capturent this
Une arrow function (=>) ne crée pas son propre this : elle garde celui de l'endroit où elle est écrite (this « lexical »). D'où le réflexe : entoure l'appel d'une arrow.
setTimeout(() => c.incr(), 100); // ✅ l'arrow garde le contexte, c.incr() est bien appelé sur c
// Dans une classe, définir une méthode-champ en arrow fige aussi this :
class Bouton {
clics = 0;
onClick = () => { this.clics++; }; // this sera TOUJOURS le bouton
}
Les anciennes solutions .bind(c), .call(c), .apply(c) existent encore (elles forcent this), mais l'arrow function est aujourd'hui le réflexe le plus simple.
Pas de règle « toujours arrow ». L'arrow ne préserve pas this, elle n'en a pas et hérite de la portée d'écriture. Une seule question décide :
- Tu l'appelles toi-même avec l'objet devant (
c.incr(), une méthode d'objet) → fonction/méthode normale.thisest déjà bon, et la méthode vit sur le prototype (partagée par toutes les instances). - Tu la passes détachée (
addEventListener,.map,setTimeout) et elle a besoin de sonthis→ arrow (() => c.incr()) ou champ-arrow. À noter : le champ-arrow est recréé pour chaque instance, donc à réserver à ce cas, pas à mettre partout par défaut.
Vois le piège (et le fix)
Sous le capot : les prototypes
Avant les classes (2015), JavaScript faisait déjà de la POO… avec les prototypes. Chaque objet a un objet « parent » (son prototype) où JS va chercher une propriété ou une méthode s'il ne la trouve pas sur lui-même. C'est la chaîne de prototypes.
La vérité qui surprend : les classes JS ne sont que du « sucre syntaxique » par-dessus les prototypes. class Compte { deposer() {} } met en réalité deposer sur Compte.prototype, partagé par toutes les instances. Tu n'as pas besoin de manipuler les prototypes à la main (la syntaxe class est plus claire), mais savoir qu'ils sont là explique pourquoi l'héritage marche et pourquoi une méthode n'est pas dupliquée dans chaque objet.
À retenir : class = présentation moderne ; prototype = mécanisme réel en dessous. Comprendre ça, c'est ne plus jamais être surpris par le comportement des objets JS.
__proto__ jusqu'à la trouver, ou jusqu'à null.this depends on WHO calls
In JavaScript, this isn't fixed: its value depends on how the function is called, not where it's written. In a method called normally (object.method()), this is the object. But if you detach the method (pass it as a callback), this gets lost.
class Compteur {
valeur = 0;
incr() { this.valeur++; console.log(this.valeur); }
}
const c = new Compteur();
c.incr(); // 1 → this = c, OK
const f = c.incr; // we DETACH the method
// f(); // ❌ this is undefined → error
setTimeout(c.incr, 100); // ❌ same problem: called "on its own"
It's THE trap every beginner hits, especially with addEventListener and setTimeout.
The fix: arrow functions capture this
An arrow function (=>) doesn't create its own this: it keeps the one from where it's written ("lexical" this). Hence the reflex: wrap the call in an arrow.
setTimeout(() => c.incr(), 100); // ✅ the arrow keeps the context, c.incr() runs on c
// In a class, defining a field-method as an arrow also pins this:
class Bouton {
clics = 0;
onClick = () => { this.clics++; }; // this will ALWAYS be the button
}
The old solutions .bind(c), .call(c), .apply(c) still exist (they force this), but the arrow function is now the simplest reflex.
There's no "always use arrow" rule. An arrow doesn't preserve this, it has none and inherits the scope where it's written. One question decides:
- You call it yourself with the object in front (
c.incr(), an object method) → normal function/method.thisis already correct, and the method lives on the prototype (shared across all instances). - You pass it detached (
addEventListener,.map,setTimeout) and it needs itsthis→ arrow (() => c.incr()) or arrow field. Note: an arrow field is recreated per instance, so save it for this case — don't sprinkle it everywhere by default.
See the trap (and the fix)
Under the hood: prototypes
Before classes (2015), JavaScript already did OOP… with prototypes. Each object has a "parent" object (its prototype) where JS looks for a property or method if it can't find it on the object itself. That's the prototype chain.
The surprising truth: JS classes are just "syntactic sugar" over prototypes. class Compte { deposer() {} } actually puts deposer on Compte.prototype, shared by all instances. You don't need to handle prototypes by hand (the class syntax is clearer), but knowing they're there explains why inheritance works and why a method isn't duplicated in every object.
Takeaway: class = modern presentation; prototype = the real mechanism underneath. Understand that, and you'll never be surprised by JS object behavior again.
__proto__ until it finds it, or hits null.🎯 Pratique
S'entraîner (clique pour ouvrir) :
⚖️ Juge le code de l'IA
L'IA te propose ce composant : un compteur de clics qui branche sa méthode incr directement comme écouteur. Ton rôle de relecteur : accepter tel quel, ou rejeter, et dire pourquoi.
class Compteur {
valeur = 0;
incr() { this.valeur++; }
}
const c = new Compteur();
bouton.addEventListener("click", c.incr);
c.incr passé seul est détaché de c. Au clic, addEventListener appelle la fonction avec this = l'élément DOM (le bouton), pas l'instance ; this.valeur++ écrit alors valeur sur le bouton et le vrai compteur ne bouge jamais. Le fix : envelopper dans une arrow, addEventListener("click", () => c.incr()), ou définir incr comme champ-arrow dans la classe pour figer this.🧠 Rappel libre
Sans remonter dans la leçon : de quoi dépend la valeur de this, pourquoi const f = c.incr; f() casse, et comment une arrow function répare le problème ?
this dépend de comment la fonction est appelée, pas d'où elle est écrite. c.incr() : this = c. Mais const f = c.incr; f() détache la méthode : elle est appelée "toute seule", donc this vaut undefined (mode strict des classes) et this.valeur lève une erreur. L'arrow () => c.incr() ne crée pas son propre this : à l'intérieur on rappelle explicitement c.incr(), donc this redevient c.Tes classes sont propres. Reste à découper ton code en fichiers réutilisables, au lieu d'un seul gros script : les ES Modules.
Leçon 3 : Les ES Modules →