bind(), call() et apply() en JavaScript : comprendre et maîtriser this

Pourquoi this change de valeur selon la façon d’appeler une fonction, et comment call(), apply() et bind() le fixent : exemples exécutés, cas des callbacks et des classes, comparaison avec les fonctions fléchées.

bind(), call() et apply() en JS
Réponse rapide

call() et apply() exécutent immédiatement une fonction avec un this choisi, call reçoit les arguments un par un, apply les reçoit dans un tableau. bind() n’exécute rien : il renvoie une nouvelle fonction dont this (et éventuellement les premiers arguments) sont fixés pour toujours. On s’en sert quand une méthode est passée en callback et perd son objet.

Toutes les fonctions JavaScript possèdent trois méthodes, call(), apply() et bind(), qui servent la même cause : décider ce que vaut this au moment où la fonction s’exécute. Pour comprendre à quoi elles servent, il faut d’abord comprendre le problème qu’elles résolvent : en JavaScript, this ne dépend pas de l’endroit où une fonction est écrite, mais de la façon dont elle est appelée.

Le problème : this change selon l’appel

Quatre règles suffisent à prédire this dans presque tous les cas :

  1. Appel de méthode objet.fonction() : this est objet.
  2. Appel simple fonction() : this est undefined en mode strict (modules, classes), l’objet global window en mode non strict.
  3. Appel avec new : this est l’objet en cours de construction.
  4. Appel via call, apply ou une fonction liée par bind : this est ce que vous avez décidé.

Les fonctions fléchées échappent à ces règles : elles n’ont pas de this propre et gardent celui du code qui les entoure.

Le cas qui fait mal en pratique est le passage de la règle 1 à la règle 2 :

probleme.js
const person = {
  name: 'Sarah',
  hello() {
    return `Bonjour ${this.name}`;
  },
};

person.hello();            // 'Bonjour Sarah'  : appel de méthode, this = person

const detached = person.hello;
detached();                // TypeError en mode strict : this vaut undefined
                           // ('Bonjour undefined' ou 'Bonjour ' en mode non strict)

La fonction n’a pas changé. Seule la manière de l’appeler a changé, et this avec elle. C’est exactement ce qui se produit quand on passe une méthode à setTimeout, à addEventListener ou à map.

call() : appeler avec un this choisi

fonction.call(thisArg, arg1, arg2, …) exécute la fonction immédiatement, avec this égal à thisArg et les arguments passés un par un.

call.js
function presenter(age, ville) {
  return `${this.name}, ${age} ans, ${ville}`;
}

const person = { name: 'Sarah' };

presenter.call(person, 30, 'Lille');           // 'Sarah, 30 ans, Lille'
presenter.call({ name: 'Léa' }, 25, 'Paris');   // 'Léa, 25 ans, Paris'

const detached = person.hello;                  // la méthode de l'exemple précédent
detached.call(person);                          // 'Bonjour Sarah' : this est rétabli

Un usage classique : réutiliser une méthode de tableau sur quelque chose qui n’en est pas un.

javascript
Array.prototype.slice.call('abc');   // ['a', 'b', 'c']
// Aujourd'hui on écrit plutôt :
Array.from('abc');                   // ['a', 'b', 'c']

apply() : la même chose, arguments dans un tableau

fonction.apply(thisArg, [arg1, arg2, …]) fait exactement ce que fait call, mais reçoit les arguments sous forme de tableau. Pratique quand ils arrivent déjà groupés.

apply.js
presenter.apply(person, [30, 'Lille']);   // 'Sarah, 30 ans, Lille'

const notes = [3, 9, 4];
Math.max.apply(null, notes);   // 9 : Math.max attend des arguments séparés, pas un tableau

Depuis ES2015, l’opérateur de décomposition rend apply rarement nécessaire : Math.max(...notes) et presenter.call(person, ...args) font le même travail en restant lisibles. On rencontre surtout apply dans du code écrit avant.

bind() : fixer this pour plus tard

Là où call et apply exécutent tout de suite, bind n’exécute rien. Il renvoie une nouvelle fonction dont this est fixé définitivement. C’est l’outil des callbacks : on prépare la fonction maintenant, quelqu’un d’autre l’appellera plus tard.

bind.js
const bound = person.hello.bind(person);

bound();                          // 'Bonjour Sarah', quel que soit le contexte d'appel
bound.call({ name: 'X' });        // 'Bonjour Sarah' : bind gagne, call ne peut plus changer this
bound.name;                       // 'bound hello'

Le cas typique : une méthode passée en callback

compteur.js
class Compteur {
  constructor() {
    this.n = 0;
  }
  incrementer() {
    this.n += 1;
    return this.n;
  }
}

const compteur = new Compteur();

// Sans bind : la méthode est détachée de son objet
const inc = compteur.incrementer;
inc();   // TypeError: Cannot read properties of undefined (reading 'n')

// Avec bind : this est fixé à compteur
const incLie = compteur.incrementer.bind(compteur);
incLie();   // 1
incLie();   // 2

Le même schéma s’applique aux minuteurs et aux événements du navigateur :

javascript
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));

// Pour pouvoir retirer l'écouteur ensuite, gardez la référence liée :
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);

Sans bind, addEventListener appellerait incrementer avec this égal au bouton cliqué : this.n serait undefined et le compteur ne compterait rien.

bind() pour l’application partielle

Les arguments passés à bind après this sont eux aussi fixés. La fonction renvoyée attend les suivants.

partiel.js
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille');   // 'Sarah, 30 ans, Lille' : seul le dernier argument reste à fournir

const add = (a, b) => a + b;
const add5 = add.bind(null, 5);   // this ne sert pas : null suffit
add5(3);                          // 8

C’est une façon simple de spécialiser une fonction générique sans écrire une fonction enveloppe.

Fonctions fléchées : quand bind devient inutile

Une fonction fléchée capture le this de l’endroit où elle est écrite et l’ignore ensuite. Deux conséquences.

D’abord, call, apply et bind ne peuvent pas changer son this :

javascript
const arrow = () => this;
arrow.call({ a: 1 });   // this du module, pas { a: 1 }

Ensuite, et c’est l’usage courant, une fléchée déclarée dans une méthode ou une classe conserve le bon this sans bind :

fleche.js
class Compteur {
  n = 0;

  // Propriété fléchée : this est capturé une fois pour toutes
  incrementer = () => {
    this.n += 1;
    return this.n;
  };
}

const c = new Compteur();
const inc = c.incrementer;
inc();   // 1 : fonctionne sans bind

setTimeout(() => c.incrementer(), 1000);   // ou une fléchée à l'appel

Revers de la médaille : une propriété fléchée est recréée pour chaque instance, alors qu’une méthode classique est partagée par le prototype. Pour quelques objets, aucune importance, pour des milliers d’instances, la méthode liée une fois dans le constructeur (this.incrementer = this.incrementer.bind(this)) économise de la mémoire.

this passé en second argument

Plusieurs méthodes de tableau acceptent directement la valeur de this à utiliser dans le callback, ce qui évite un bind :

javascript
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config);   // [10, 20, 30]

forEach, filter, some et every ont le même second argument. Avec une fonction fléchée, il devient inutile.

Tableau comparatif

call apply bind
Exécute la fonction immédiatement immédiatement non : renvoie une fonction
Arguments un par un dans un tableau un par un, fixés pour les appels futurs
Usage typique emprunter une méthode étaler un tableau (remplacé par ...) callbacks, événements, application partielle
Effet sur une fonction fléchée aucun sur this aucun sur this aucun sur this, les arguments sont fixés

Erreurs fréquentes et comment les lire

  • « Cannot read properties of undefined (reading ‘n’) » dans une méthode de classe : la méthode a été détachée de son instance (règle 2). Liez-la avec bind ou déclarez-la en propriété fléchée.
  • Un écouteur d’événement qui ne modifie pas l’état : this est l’élément DOM, pas votre objet. Même remède.
  • Un bind qui semble ne rien faire : la fonction est une fléchée. Retirez le bind, il est inutile.
  • removeEventListener qui ne retire rien : chaque bind crée une nouvelle fonction, il faut passer la même référence liée à l’ajout et au retrait.

Pour la suite, les classes en JavaScript reviennent sur les méthodes et le prototype, et le chapitre Les fonctions du tutoriel pose les bases si le vocabulaire de cet article est encore neuf. En PHP, la question se pose autrement : $this ne change jamais d’objet.

Erreurs fréquentes

Passer une méthode en callback sans bind bouton.addEventListener('click', compteur.incrementer) appelle la fonction avec this égal au bouton, pas à compteur. Écrivez compteur.incrementer.bind(compteur) ou une fonction fléchée.
Croire que bind sur une fonction fléchée change this Une fonction fléchée n’a pas de this propre : call, apply et bind ne le modifient pas. Seuls les arguments passés par bind sont pris en compte.
Appeler bind à chaque rendu bind crée une nouvelle fonction à chaque appel. Dans une boucle ou un rendu répété, liez une fois dans le constructeur (ou déclarez la méthode comme propriété fléchée).
Confondre call et apply avec la même liste d’arguments f.call(obj, [1, 2]) passe un seul argument, le tableau. Pour étaler un tableau, c’est apply, ou f.call(obj, ...tableau).

JavaScript

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.