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 peux 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, et 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 et 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 ? Et 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