Utilisez la méthode Function.prototype.bind() de Javascript

Chaque développeur javascript a un jour ou l’autre rencontré un problème avec le contexte associé à this.

Prenons l’exemple de code simpliste suivant :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  }
};

myObject.doSomethingCrazy();

Mon objet contient une propriété crazyMessage et une méthode doSomethingCrazy qui utilise la propriété crazyMessage.
Jusqu’ici tout va bien, le contexte de this au sein de la méthode doSomethingCrazy est bien l’objet myObject.

Maintenant, dans un élan de folie, je veux implémenter la méthode doSomeAsyncCrazyness :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Aussi surprenant que cela puisse paraître, je récupère une erreur dans ma console :

Uncaught TypeError: undefined is not a function 

Cette erreur est due à this qui est désormais associé au contexte global et non plus à mon objet myObject, à cause de l’appel à setTimeout.

Mettre this en « cache »

Pour pallier ce problème, l’approche très souvent adoptée par le développeur est d’assigner this à une variable intermédiaire temporaire de type that ou self de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {

    var that = this;
    setTimeout(function() {
      that.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

De cette manière, l’appel à la méthode doSomeAsyncCrazyness fonctionne en effet comme prévu.

Une méthode bien plus élégante existe cependant et ce depuis ECMAScript 5.

.bind() magic

La méthode .bind() disponible sur le prototype de Function résoud notre problème en permettant, entre autres choses, de définir explicitement le contexte associé à this.

Nous pouvons donc réécrire notre code précédent de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }.bind(this), 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Avouez que c’est quand même plus joli.

Support des vieux navigateurs

Et si mon code doit s’exécuter dans un navigateur qui ne supporte pas ES5 ? Internet Explorer < 9 pour ne pas le nommer.
Dans ce cas, plusieurs solutions.

  • Continuer avec la technique de mise en cache de this
  • Utiliser la méthode .bind implémentée dans la plupart des frameworks JS (Angular.bind() par exemple)
  • Implémenter un fallback quand la méthode n’est pas disponible

Mozilla notamment propose le fallback suivant (Mozilla polyfill Function.prototype.bind()) :

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - 
           what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Pour aller plus loin

Quelques ressources pour aller plus loin :

Cette entrée a été publiée dans Non classé le par .

À propos Olivier Balais

Jeune ingénieur logiciel basé à Lyon (@overnetcity) passionné par les NTIC et le développement Web, je suis actuellement salarié chez Reputation VIP et effectue en parallèle des missions ponctuelles en temps que Freelance. Passionné depuis toujours par l'informatique et le développement, suite à une formation solide à l'INSA de Lyon, je me suis spécialisé dans la réalisation de bout en bout de projets web complexes.

Une réflexion au sujet de « Utilisez la méthode Function.prototype.bind() de Javascript »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *