Aucun mot-clé du langage ne provoque autant de confusion. La raison est simple : dans la plupart des langages, this est fixé à l'écriture, alors qu'en JavaScript il est déterminé à l'appel.
Une même fonction peut donc voir plusieurs valeurs différentes selon la manière dont on la déclenche.
Définition
this désigne l'objet sur lequel une fonction est en train d'être appelée. Ce n'est pas l'objet dans lequel la fonction est écrite, c'est celui qui figure à gauche du point au moment de l'appel.
const compteur = {
total: 0,
incrementer() {
this.total += 1;
return this.total;
},
};
console.log(compteur.incrementer()); // 1
console.log(compteur.incrementer()); // 2Ici, compteur se trouve à gauche du point, donc this le désigne, et l'incrément porte bien sur cet objet.
Cinq formes d'appel, cinq résultats
Le tableau ci-dessous se lit comme une grille : cherchez la forme de l'appel, lisez la valeur obtenue.
| Forme de l'appel | Valeur de this |
|---|---|
| objet.methode() | L'objet placé avant le point |
| fonction() dans un module | undefined |
| new Modele() | L'instance en cours de fabrication |
| fonction.call(cible) | La cible passée en premier argument |
| Fonction fléchée | Celui du code environnant |
La deuxième ligne mérite une précision : en mode strict ou dans un module, un appel sans objet donne undefined. Dans un vieux script non modulaire, la même ligne donne l'objet global.
Le piège de la méthode détachée
Ranger une méthode dans une variable ou la passer en argument coupe le lien avec son objet. C'est la cause la plus fréquente des erreurs sur ce mot-clé.
const compteur = {
total: 0,
incrementer() { this.total += 1; return this.total; },
};
const detachee = compteur.incrementer;
try {
detachee(); // plus rien à gauche du point
} catch (erreur) {
console.log(erreur.name); // TypeError
}
const liee = compteur.incrementer.bind(compteur);
console.log(liee()); // 1La méthode bind fabrique une nouvelle fonction dont la valeur est figée définitivement. L'autre solution, souvent plus lisible, consiste à envelopper l'appel dans une fonction fléchée : elle n'a pas de valeur propre et prend celle du code qui l'entoure.
const minuteur = {
secondes: 0,
demarrer() {
setTimeout(() => {
this.secondes += 1;
console.log(this.secondes); // 1
}, 0);
},
};
minuteur.demarrer();Avec une function classique à la place de la flèche, l'addition donnerait NaN : c'est setTimeout qui appelle la fonction, sans objet à gauche du point.
Questions fréquentes
Pourquoi une fonction fléchée n'a-t-elle pas de this ?
Parce qu'elle n'en reçoit jamais à l'appel : elle utilise celui du code dans lequel elle est écrite, exactement comme n'importe quelle autre variable de ce code. C'est ce qui la rend idéale à l'intérieur d'une méthode, et inadaptée pour définir une méthode elle-même, où elle irait chercher une valeur qui n'existe pas.
Quelle différence entre call, apply et bind ?
call et apply appellent la fonction immédiatement en imposant la valeur, la seule différence étant la façon de passer les arguments, un par un ou dans un tableau. bind n'appelle rien : il renvoie une nouvelle fonction avec la valeur figée, à déclencher plus tard.
Comment savoir ce que vaut this dans du code que je lis ?
Ne regardez pas la déclaration de la fonction, cherchez l'appel. La question à poser est toujours la même : qu'y a-t-il à gauche du point au moment où cette fonction est déclenchée. Notre formation JavaScript revient longuement sur ce mécanisme, parce qu'il conditionne la lecture de tout code orienté objet.