JavaScript : Comprendre et utiliser call, apply et bind
#1 D'où sortent call, apply et bind ?
Une précision préliminaire au niveau du vocabulaire. Dans ce tuto, j'utilise indifféremment les mots instance ou objet, car ils veulent dire la même chose et j'utilise aussi indifféremment les mots référence ou pointeur, car ils veulent dire la même chose.
Je vais prendre la console et je vais déclarer, par exemple, une fonction que je vais appeler calculerSomme
. Si je fais un console.log
de calculerSomme
, je vois la fonction. Maintenant si je fais un console.dir
, je vois l'objet Function
. Maintenant, je déploie et je peux voir que call
, apply
et bind
sont des méthodes qui se trouvent dans le prototype du constructeur Function
. Donc là, c'est le moment de vous souvenir qu'en JavaScript les fonctions sont des objets si vous voulez vous y retrouver. Sinon, ça coince, car là, vous vous demandez d'où sortent ce prototype et ces méthodes call
, apply
ou bind
, qui se trouvent dedans.
A quoi vont servir ces méthodes ? Elles vont servir à affecter la valeur de this
au moment de l'appel d'une fonction.
#2 Comprendre et utiliser call ou apply
Mieux vaut faire un exemple, plutôt que de se lancer dans une explication.
- Je vais déclarer un objet
obj
avec une propriétésomme
initialisée à0
. - Maintenant, je décide de fabriquer une fonction
calculerSomme
, qui a pour objectif de calculer la somme de ses arguments. Par contre, ce que je veux, c'est utiliserthis
pour passerobj
à cette fonction. De cette manière, je vais pouvoir vous montrer que l'on peut affecterthis
. (* Pour faire ce calcul je pourrais évidemment passerobj
en argument decalculerSomme
ou créer une méthode dansobj
, mais ce n'est pas l'objectif de cet exemple.) - Pour que
this
soit égal àobj
dans la fonctioncalculerSomme
, je me sers de la méthodecall
, et je lui passe en argumentobj
suivit des arguments que je veux passer àcalculerSomme
.
Ci-dessous, ce qu'il faut comprendre, c'est que le calculerSomme.call(obj,2,4,6)
est équivalent à un calculerSomme(2,4,6)
avec un obj
qui est affecté à this
et les arguments 2,4,6
qui sont affectées à ...data
.
Petite précision : étant donné que this
reçoit la valeur de obj
qui est un pointeur, les deux pointeront sur le même objet en mémoire c'est-à-dire l'objet qui contient la propriété somme
.
Maintenant, je passe à un autre cas de figure. Si dans notre exemple, les données à ajouter s'étaient présentées sous la forme d'un tableau, eh bien, vous auriez eu avantage à utiliser apply
. Pourquoi ? En fait, apply
fonctionne strictement de la même manière que call
, mais prend la liste des arguments à passer à la fonction, sous la forme d'un tableau. Donc, si vous avez un tableau à disposition, eh bien, vous prenez apply
plutôt que call
tout simplement !
#3 Comprendre et utiliser bind
La méthode bind
est une autre façon d'affecter this
. L'avantage avec bind
c'est que les choses se font en deux étapes :
- Dans un premier temps, je vais appeler
bind
. Je vais lui passer en argument l'objet sur lequel je veux que pointethis
donc iciobj
. LecalculerSomme.bind(obj)
va me renvoyer une copie de la fonctioncalculerSomme
avec unthis
de correctement géré. - Dans un deuxième temps, c'est cette copie que je vais invoquer et je vais lui passer les arguments.
#4 Utiliser bind dans les callback
Les appels à la méthode bind
sont très utilisés dans les requêtes asynchrones. Dans ce cas, le bind
est utilisé au niveau de la fonction callback pour faire en sorte évidemment que le callback s'exécute avec une valeur de this
qui lui convienne.
Pourquoi le callback est-il un cas typique d'utilisation du bind ? Eh bien, parce qu'il correspond à une utilisation en deux temps :
- Dans un premier temps, on a le passage d'une fonctionnalité en argument d'un traitement asynchrone. Ca, c'est une étape qui correspond au passage du callback en argument.
- Dans un deuxième temps, on a l'exécution de ce traitement. Ca, c'est l'étape qui correspond à l'invocation du callback.
Je vais reprendre l'exemple d'un gestionnaire d'événement, sur lequel j'ai déjà travaillé dans un autre tuto, de manière à pouvoir un peu plus le détailler.
Dans l'exemple ci-dessous, je déclare une classe, je déclare sa fonction constructeur, je lui ajoute une propriété click
qui est un booléen que j'initialise à la valeur false
. Je crée la méthode set
qui servira à placer la valeur de click
à true
. Ensuite, je crée un objet en instanciant la classe. Le but du truc, c'est de mémoriser le fait qu'il y a eu un click
sur le bouton.
On peut voir dans le tuto dédié à this
que si je passe la méthode obj.set
en tant que callback de addEventListener
alors obj.set
s'exécute avec un this
qui a la valeur but
. Dans ces conditions, et de manière à ce que this
soit correctement positionné, je vais faire un obj.set.bind(obj)
.
Donc dans un premier temps, j'obtiens avec mon bind
une copie de obj.set
avec le this
de positionné sur obj
. Et dans un deuxième temps, la méthode obj.set
est invoquée dans les conditions que je souhaite au niveau du gestionnaire d'événement.