close

JavaScript : Comprendre et utiliser call, apply et bind

#1 D'où sortent call, apply et bind ? open

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.

Call, Apply, Bind

#2 Comprendre et utiliser call ou apply open

Mieux vaut faire un exemple, plutôt que de se lancer dans une explication.

  1. Je vais déclarer un objet obj avec une propriété somme initialisée à 0.
  2. 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 utiliser this pour passer obj à cette fonction. De cette manière, je vais pouvoir vous montrer que l'on peut affecter this. (* Pour faire ce calcul je pourrais évidemment passer obj en argument de calculerSomme ou créer une méthode dans obj, mais ce n'est pas l'objectif de cet exemple.)
  3. Pour que this soit égal à obj dans la fonction calculerSomme, je me sers de la méthode call, et je lui passe en argument obj suivit des arguments que je veux passer à calculerSomme.
Tester le code
Résultat de l'exécution
Résultat de l'exécution du call

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.

Schéma du call

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.

this et obj sont des pointeurs

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 !

Tester le code

#3 Comprendre et utiliser bind open

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 :

  1. Dans un premier temps, je vais appeler bind. Je vais lui passer en argument l'objet sur lequel je veux que pointe this donc ici obj. Le calculerSomme.bind(obj) va me renvoyer une copie de la fonction calculerSomme avec un this de correctement géré.
  2. Dans un deuxième temps, c'est cette copie que je vais invoquer et je vais lui passer les arguments.
Tester le code
Le console.log de la fonction retournée par bind
Un console.log de la fonction retournée par bind
Le console.dir de la fonction retournée par bind
Un console.dir de la fonction retournée par bind

#4 Utiliser bind dans les callback open

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 :

  1. 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.
  2. 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.

Tester le code
Vous pouvez constater ci-dessous la modification de l'objet grâce à l'appel à bind
Solution du tuto précédent avec un appel à bind