89 votes

La préservation d'une référence à "ce" en JavaScript prototype des fonctions

Je suis juste en utilisant des prototypes JavaScript et j'ai du mal à comprendre comment préserver un this référence à l'objet principal de l'intérieur d'un prototype de la fonction lorsque la portée des changements. Permettez-moi d'illustrer ce que je veux dire (je suis en utilisant jQuery ici):

MyClass = function() {
  this.element = $('#element');
  this.myValue = 'something';

  // some more code
}

MyClass.prototype.myfunc = function() {
  // at this point, "this" refers to the instance of MyClass

  this.element.click(function() {
    // at this point, "this" refers to the DOM element
    // but what if I want to access the original "this.myValue"?
  });
}

new MyClass();

Je sais que je peux conserver une référence à l'objet principal en faisant cela, au début de l' myfunc:

var myThis = this;

et puis, à l'aide de myThis.myValue pour accéder à l'objet principal de la propriété. Mais ce qui arrive quand j'ai un tas de prototypes de fonctions sur MyClass? Dois-je enregistrer la référence à l' this au début de chacun? Semble comme il devrait y avoir une manière plus propre. Et à propos d'une situation comme celle-ci:

MyClass = function() {
  this.elements $('.elements');
  this.myValue = 'something';

  this.elements.each(this.doSomething);
}

MyClass.prototype.doSomething = function() {
  // operate on the element
}

new MyClass();

Dans ce cas, je ne peux pas créer une référence à l'objet principal avec var myThis = this; parce que même la valeur d'origine de l' this dans le contexte de l' doSomething est jQuery objet et non pas un MyClass objet.

Il a été suggéré de m'utiliser une variable globale pour contenir la référence à l'original, this, mais cela semble être une très mauvaise idée pour moi. Je ne veux pas polluer l'espace de noms global et qui semble comme il ne pourrait m'empêcher de l'instanciation de deux MyClass objets sans eux, sans interférer les uns avec les autres.

Toutes les suggestions? Est-il un moyen propre à faire ce que je suis après? Ou est mon modèle de conception viciée?

64voto

CMS Points 315406

Pour la préservation de l'environnement, l' bind méthode est vraiment utile, il fait maintenant partie de la récemment publié ECMAScript 5ème Édition de la Spécification, de la mise en œuvre de cette fonction est simple (8 lignes):

// The .bind method from Prototype.js 
if (!Function.prototype.bind) { // check if native implementation available
  Function.prototype.bind = function(){ 
    var fn = this, args = Array.prototype.slice.call(arguments),
        object = args.shift(); 
    return function(){ 
      return fn.apply(object, 
        args.concat(Array.prototype.slice.call(arguments))); 
    }; 
  };
}

Et vous pouvez l'utiliser, dans votre exemple, comme ceci:

MyClass.prototype.myfunc = function() {

  this.element.click((function() {
    // ...
  }).bind(this));
};

Un autre exemple:

var obj = {
  test: 'obj test',
  fx: function() {
    alert(this.test + '\n' + Array.prototype.slice.call(arguments).join());
  }
};

var test = "Global test";
var fx1 = obj.fx;
var fx2 = obj.fx.bind(obj, 1, 2, 3);

fx1(1,2);
fx2(4, 5);

Dans ce second exemple, nous pouvons observer plus sur le comportement de l' bind.

Essentiellement, il génère une nouvelle fonction, qui sera le responsable de l'appel de notre fonction, le maintien de la fonction de contexte (this de la valeur), qui est défini comme le premier argument de bind.

Le reste des arguments sont simplement transmis à notre fonction.

Remarque dans cet exemple que la fonction fx1, est appelée sans aucun contexte de l'objet (obj.method() ), tout comme un simple appel à la fonction, dans ce type de invokation, l' this mot-clé à l'intérieur vont se référer à l'objet Global, il alertera "test global".

Maintenant, l' fx2 est la nouvelle fonction que l' bind méthode généré, il va appeler notre fonction de préserver le contexte et correctement en passant les arguments, il alerte "obj test 1, 2, 3, 4, 5" parce que nous avons invoqué l'ajout de deux de plus d'arguments, il a déjà appliqué les trois premiers.

10voto

icktoofay Points 60218

Pour votre dernière MyClass exemple, vous pourriez faire ceci:

var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });

Dans la fonction qui est passé de each, this se réfère à un objet jQuery, comme vous le savez déjà. Si à l'intérieur de cette fonction, vous obtenez l' doSomething fonction à partir d' myThis, et ensuite appeler la méthode appliquer sur cette fonction avec les arguments un tableau (voir l' apply de la fonction et de l' arguments variable), alors this sera mis à l' myThis en doSomething.

7voto

Nemesarial Points 11

Je me rends compte que c'est un vieux thread, mais j'ai une solution beaucoup plus élégante, et il a quelques inconvénients, outre le fait qu'il n'est généralement pas fait, comme je l'ai remarqué.

Considérez les points suivants:

var f=function(){
    var context=this;
}    

f.prototype.test=function(){
    return context;
}    

var fn=new f();
fn.test(); 
// should return undefined because the prototype definition
// took place outside the scope where 'context' is available

Dans la fonction ci-dessus, nous avons défini une variable locale (contexte). Nous avons ensuite ajouté un prototype de la fonction (test) qui retourne la variable locale. Comme vous l'avez probablement prédit, lorsque nous créons une instance de cette fonction, puis d'exécuter la méthode d'essai, il ne retourne pas la variable locale parce que lorsque nous avons défini le prototype de la fonction en tant que membre de notre fonction principale, c'était hors de la portée où la variable locale est définie. C'est un problème général avec la création de fonctions, puis en ajoutant des prototypes - vous ne pouvez pas accéder à tout ce qui a été créé dans le champ d'application de la fonction principale.

Pour créer des méthodes qui sont à l'intérieur de la portée de la variable locale, nous avons besoin de définir directement des membres de la fonction et de se débarrasser de la prototypique de référence:

var f=function(){
    var context=this;    

    this.test=function(){
        console.log(context);
        return context;
    };
}    

var fn=new(f);
fn.test(); 
//should return an object that correctly references 'this'
//in the context of that function;    

fn.test().test().test(); 
//proving that 'this' is the correct reference;

Vous pourriez être inquiet que parce que les méthodes ne sont pas créés fait, les différentes instances ne peuvent pas vraiment être séparés. Pour démontrer qu'ils sont, considérez ceci:

var f=function(val){
    var self=this;
    this.chain=function(){
        return self;
    };    

    this.checkval=function(){
        return val;
    };
}    

var fn1=new f('first value');
var fn2=new f('second value');    

fn1.checkval();
fn1.chain().chain().checkval();
// returns 'first value' indicating that not only does the initiated value remain untouched,
// one can use the internally stored context reference rigorously without losing sight of local variables.     

fn2.checkval();
fn2.chain().chain().checkval();
// the fact that this set of tests returns 'second value'
// proves that they are really referencing separate instances

Une autre façon d'utiliser cette méthode consiste à créer des singletons. Le plus souvent, nos fonctions javascript ne sont pas instanciés plus d'une fois. Si vous savez que vous n'aurez jamais une deuxième instance de la même fonction, il y a ensuite un aperçu schématique de les créer. Soyez averti, cependant: les peluches va se plaindre qu'il est bizarre de construction, et à la question de votre utilisation du mot-clé "new":

fn=new function(val){
    var self=this;
    this.chain=function(){
        return self;
    };        

    this.checkval=function(){
        return val;
    };  
}    

fn.checkval();
fn.chain().chain().checkval();

Pro: Les avantages de l'utilisation de cette méthode pour créer des objets de fonction sont abondantes.

  • Il rend votre code plus facile à lire, car il tirets, les méthodes d'un objet de fonction dans une manière qui le rend visuellement plus facile à suivre.
  • Il permet d'accéder à la localement des variables définies uniquement dans les méthodes définies, à l'origine de cette façon , même si vous ajoutez ultérieurement prototype de fonctions ou même des fonctions de membre de la fonction de l'objet, il ne peut pas accéder aux variables locales et quelles que soient les fonctionnalités ou les données que vous stockez sur ce niveau demeure sûr et inaccessible à partir de n'importe où ailleurs.
  • Il permet d'une façon simple et directe manière de définir des singletons.
  • Il vous permet de stocker une référence à 'ce' et de maintenir cette référence indéfiniment.

Con: Il y a certains inconvénients à l'utilisation de cette méthode. Je ne prétends pas être exhaustif :)

  • Parce que les méthodes sont définies comme des membres à l'objet et non pas des prototypes - l'héritage peut être réalisé à l'aide de définition du membre mais pas prototypique définitions. Dans les exemples ci-dessus, si vous avez essayé de redéfinir checkval, il ne peut être fait à partir d' f.prototype.checkval=function(){} , puisque l'objet est déjà membre de ce nom et un vrai membre remplace un prototype de référence.

4voto

Gabriel McAdams Points 22323

Vous pouvez définir la portée à l'aide de la call() et apply() fonctions

1voto

Jonathan Sampson Points 121800

Puisque vous êtes à l'aide de jQuery, il est intéressant de noter que this est déjà maintenu par jQuery lui-même:

$("li").each(function(j,o){
  $("span", o).each(function(x,y){
    alert(o + " " + y);
  });
});

Dans cet exemple, o représente l' li, alors que y représente l'enfant span. Et avec $.click(), vous pouvez obtenir le champ d'application de l' event objet:

$("li").click(function(e){
  $("span", this).each(function(i,o){
    alert(e.target + " " + o);
  });
});

e.target représente l' li, et o représente l'enfant span.

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