58 votes

Portée de la fonction de rappel JavaScript

J'ai quelques problèmes avec le vieux JavaScript (sans 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, lorsque je crée un nouvel objet (après que le DOM ait été chargé, avec un span#test)

var x = new foo('test');

Le 'this' dans la fonction onclick pointe vers le span#test et non vers l'objet foo.

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

81voto

hishadow Points 990

(on a extrait quelques explications qui étaient cachées dans les commentaires de l'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 qui sera utilisé comme callback. Lorsque l'événement se déclenche, la fonction est appelée, mais elle n'est plus associée à aucun objet (this).

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

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

Puisque la variable self a été assignée ce lorsque la fermeture a été créée, 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 ce ):

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

Le code mis à jour serait alors le suivant :

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

Function.prototype.bind fait partie de l'ECMAScript 5 et offre la même fonctionnalité. Vous pouvez donc le faire :

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

Pour les navigateurs qui ne supportent pas encore ES5, MDN fournit la cale suivante :

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;  
  };  
}

15voto

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

5voto

Seldaek Points 12311

Pour le jQuery les utilisateurs qui cherchent une solution à ce problème, vous devez utiliser jQuery.proxy

2voto

Vincent Robert Points 16530

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

JavaScript ne dispose que de fonctions et pas de délégués comme C#, il n'est donc pas possible de passer une méthode ET l'objet auquel elle doit être appliquée comme callback.

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

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

2voto

tpk Points 823

Une bonne explication du problème (j'ai eu du mal à comprendre les solutions décrites jusqu'ici) est la suivante disponible ici .

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