Une méthode fonctionne parfaitement, puis on la passe en argument et elle échoue. Le code n'a pas changé d'une ligne. Ce qui a changé, c'est la façon dont elle est appelée.
C'est la particularité de this en JavaScript : sa valeur n'est pas décidée là où la fonction est écrite, mais là où elle est appelée. On appelle cette résolution la liaison de this.
Définition
La liaison de this est la règle qui détermine, au moment de l'appel, quel objet la fonction désignera par this. Quatre formes d'appel existent, et une cinquième qui échappe entièrement à la règle.
| Forme de l'appel | Ce que vaut this |
|---|---|
new Compte() | L'objet fraîchement créé |
compte.presenter() | L'objet écrit avant le point |
presenter.call(compte) | Le premier argument fourni |
presenter() | undefined en module, l'objet global sinon |
() => ... | Celui de l'endroit où la flèche a été écrite |
Ce tableau à deux colonnes se lit de haut en bas : la première forme qui correspond à l'appel l'emporte.
Le cas qui casse, et pourquoi
const compte = {
nom: "Believemy",
presenter() {
return this;
},
};
console.log(compte.presenter() === compte); // true
const detache = compte.presenter;
console.log(detache() === compte); // false
const relie = compte.presenter.bind(compte);
console.log(relie() === compte); // trueLa méthode n'appartient pas à l'objet, elle y est simplement rangée. Sortie de son emplacement, elle devient une fonction ordinaire appelée sans point, donc sans objet à désigner. Dans un module, this y vaut undefined et toute lecture de propriété lève une TypeError.
Les trois façons de refixer la liaison
bind()renvoie une nouvelle fonction dont lethisest figé. C'est la solution quand la fonction part ailleurs.call()etapply()appellent immédiatement en imposant lethis. Les deux ne diffèrent que par la façon de passer les arguments.- La Fonction fléchée n'a pas de
thispropre : elle reprend celui de son environnement d'écriture, ce qui règle le problème sans y penser.
const minuteur = {
nom: "chrono",
demarrer() {
setTimeout(() => {
console.log("fini : " + this.nom);
}, 10);
},
};
minuteur.demarrer(); // fini : chronoAvec une fonction classique à la place de la flèche, this.nom ne désignerait plus le minuteur : la fonction de rappel est appelée par le moteur, sans objet devant.
Questions fréquentes
Pourquoi ma méthode de classe perd-elle son this en argument ?
Parce que passer objet.methode en argument transmet la fonction seule, détachée de l'objet. La correction consiste à passer objet.methode.bind(objet), ou à envelopper l'appel dans une flèche. Dans une class, déclarer la méthode comme un champ fléché fige la liaison une fois pour toutes.
Quelle différence entre call() et apply() ?
Aucune sur le résultat. call() reçoit les arguments un par un, apply() les reçoit dans un tableau. Depuis l'arrivée du Spread (...), apply() a perdu presque tout intérêt, puisque fonction.call(cible, ...liste) fait la même chose de façon plus lisible.
Peut-on rattacher une fonction fléchée à un autre objet ?
Non, et c'est voulu. Une flèche ne possède pas de liaison propre, donc bind(), call() et apply() n'ont aucun effet sur son this. C'est exactement pour cela qu'elle est fiable comme fonction de rappel, et inadaptée comme méthode d'objet ou comme constructeur.