42 votes

Maintenir la référence à "this" en Javascript lors de l'utilisation de rappels et de fermetures

Je me retrouve à assigner "this" à une variable pour pouvoir l'utiliser facilement dans les rappels et les fermetures.

Est-ce une mauvaise pratique? Existe-t-il une meilleure façon de faire référence à la fonction d'origine?

Voici un exemple typique.

User.prototype.edit = function(req, res) {

  var self = this,
      db = this.app.db;

  db.User.findById('ABCD', function(err, user)) {

    // Je ne peux pas utiliser this.foo(user)
    self.foo(user);
  });
};

User.prototype.foo = function(user) {

};

Utilisez-vous normalement cette approche ou avez-vous trouvé une solution plus propre?

87voto

hugomg Points 29789

Il existe trois principales façons de traiter this dans les rappels :

1. Créez une variable à portée lexicale, comme vous le faites actuellement

Les deux noms les plus courants pour cette nouvelle variable sont that et self. Personnellement, je préfère utiliser that car les navigateurs ont une propriété globale de la fenêtre appelée self et mon linter se plaint si je l'ombre.

function edit(req, res) {
    var that = this,
    db.User.findById('ABCD', function(err, user){
        that.foo(user);
    });
};

Un avantage de cette approche est que une fois que le code est converti pour utiliser that, vous pouvez ajouter autant de rappels internes que vous le souhaitez et ils fonctionneront tous parfaitement en raison de la portée lexicale. Un autre avantage est que c'est très simple et cela fonctionnera même sur d'anciens navigateurs.

2. Utilisez la méthode .bind().

Les fonctions Javascript ont une méthode .bind() qui vous permet de créer une version d'elles avec un this fixe.

function edit(req, res) {
    db.User.findById('ABCD', (function(err, user){
        this.foo(user);
    }).bind(this));
};

En ce qui concerne la gestion de this, la méthode bind est particulièrement utile pour les rappels ponctuels où l'ajout d'une fonction d'enrobage serait plus verbeux :

setTimeout(this.someMethod.bind(this), 500);

var that = this;
setTimeout(function(){ that.doSomething() }, 500);

Le principal inconvénient de bind est que si vous avez des rappels imbriqués, vous devez également appeler bind sur eux. De plus, IE <= 8 et certains autres anciens navigateurs, n'implémentent pas nativement la méthode bind donc vous pourriez avoir besoin d'utiliser une sorte de bibliothèque de shim si vous devez toujours les prendre en charge.

3. Si vous avez besoin d'un contrôle plus fin de la portée des fonctions ou des arguments, revenez à .call() et .apply()

Les façons plus primitives de contrôler les paramètres des fonctions en Javascript, y compris le this, sont les méthodes .call() et .apply(). Elles vous permettent d'appeler une fonction avec n'importe quel objet comme leur this et n'importe quelles valeurs comme ses paramètres. apply est particulièrement utile pour implémenter des fonctions variadiques, car elle reçoit la liste d'arguments sous forme d'array.

Par exemple, voici une version de bind qui reçoit la méthode à lier sous forme de chaîne. Cela nous permet d'écrire le this une seule fois au lieu de deux.

function myBind(obj, funcname){
     return function(/**/){
         return obj[funcname].apply(obj, arguments);
     };
}

setTimeout(myBind(this, 'someMethod'), 500);

2voto

Tomasz Nurkiewicz Points 140462

Malheureusement, c'est la façon bien établie de faire cela, bien que that soit une convention de nommage répandue pour ce "copier".

Vous pouvez aussi essayer :

db.User.findById('ABCD', this.foo.bind(this));

Mais cette approche nécessite que foo() ait exactement la même signature que celle attendue par findById() (dans votre exemple, vous sautez err).

2voto

Yoshi Points 25790

Vous pouvez créer un proxy pour le rappel avec:

var createProxy = function(fn, scope) {
  return function () {
    return fn.apply(scope, arguments);
  }; 
};

En utilisant ceci, vous pourriez faire ce qui suit:

db.User.findById('ABCD', createProxy(function(err, user)) {
  this.foo(user);
}, this));

jQuery fait quelque chose de similaire avec: $.proxy

Et, comme d'autres l'ont noté en utilisant bind, regardez ici si la compatibilité est un problème:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind#Compatibility

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X