web-dev-qa-db-fra.com

Portée de rappel JavaScript

J'ai des problèmes avec le vieux JavaScript (pas de frameworks) pour référencer mon objet dans une fonction de rappel.

function foo(id) {
    this.dom = document.getElementById(id);
    this.bar = 5;
    var self = this;
    this.dom.addEventListener("click", self.onclick, false);
}

foo.prototype = {
    onclick : function() {
        this.bar = 7;
    }
};

Maintenant, quand je crée un nouvel objet (après le chargement du DOM, avec un test span #)

var x = new foo('test');

Le 'ceci' à l'intérieur de la fonction onclick pointe vers le test span # et non l'objet foo.

Comment puis-je obtenir une référence à mon objet foo dans la fonction onclick?

58
Chris MacDonald

(extrait une explication qui était cachée dans les commentaires dans une autre réponse)

Le problème réside dans la ligne suivante:

this.dom.addEventListener("click", self.onclick, false);

Ici, vous passez un objet fonction à utiliser comme rappel. Lorsque l'événement se déclenche, la fonction est appelée mais maintenant elle n'a plus d'association avec aucun objet (this).

Le problème peut être résolu en enveloppant la fonction (avec sa référence d'objet) dans une fermeture comme suit:

this.dom.addEventListener(
  "click",
  function(event) {self.onclick(event)},
  false);

Puisque la variable self a été assignée ceci lors de la création de la fermeture, la fonction de fermeture se souviendra de la valeur de la variable self lorsqu'elle sera appelée ultérieurement.

Une autre façon de résoudre ce problème est de créer une fonction d'utilité (et d'éviter d'utiliser des variables pour la liaison this ):

function bind(scope, fn) {
    return function () {
        fn.apply(scope, arguments);
    };
}

Le code mis à jour ressemblerait alors à:

this.dom.addEventListener("click", bind(this, this.onclick), false);

Function.prototype.bind fait partie d'ECMAScript 5 et fournit les mêmes fonctionnalités. Vous pouvez donc faire:

this.dom.addEventListener("click", this.onclick.bind(this), false);

Pour les navigateurs qui ne prennent pas encore en charge ES5, MDN fournit le module d'interface suivant :

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  
                                 ? this  
                                 : oThis || window,  
                               aArgs.concat(Array.prototype.slice.call(arguments)));  
        };  

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

    return fBound;  
  };  
} 
81
hishadow
this.dom.addEventListener("click", function(event) {
    self.onclick(event)
}, false);
15
Sergey Ilinsky

Pour les utilisateurs jQuery à la recherche d'une solution à ce problème, vous devez utiliser jQuery.proxy

5
Seldaek

L'explication est que self.onclick ne signifie pas ce que vous pensez que cela signifie en JavaScript. Cela signifie en fait la fonction onclick dans le prototype de l'objet self (sans faire référence à self lui-même).

JavaScript n'a que des fonctions et aucun délégué comme C #, il n'est donc pas possible de passer une méthode ET l'objet auquel elle doit être appliquée en tant que rappel.

La seule façon d'appeler une méthode dans un rappel est de l'appeler vous-même dans une fonction de rappel. Les fonctions JavaScript étant des fermetures, elles peuvent accéder aux variables déclarées dans l'étendue dans laquelle elles ont été créées.

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);
2
Vincent Robert

Une bonne explication du problème (j'ai eu des problèmes pour comprendre les solutions décrites jusqu'à présent) est disponible ici .

2
tpk

J'ai écrit ce plugin ...

je pense que ce sera utile

jquery.callback

1
alberto