Je commence tout juste à utiliser du JavaScript prototypique et je ne parviens pas à comprendre comment conserver une référence this
à l'objet principal dans une fonction prototype lorsque la portée change. Permettez-moi d'illustrer ce que je veux dire (j'utilise 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 ceci au début de myfunc
:
var myThis = this;
puis en utilisant myThis.myValue
pour accéder à la propriété de l'objet principal. Mais que se passe-t-il lorsque j'ai un tas de fonctions de prototypes sur MyClass
? Dois-je enregistrer la référence à this
au début de chacun? On dirait qu'il devrait y avoir un moyen plus propre. Et que dire 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 de référence à l'objet principal avec var myThis = this;
car même la valeur d'origine de this
dans le contexte de doSomething
est un objet jQuery
et non un objet MyClass
.
On m'a suggéré d'utiliser une variable globale pour conserver la référence à la variable this
originale, mais cela me semble une très mauvaise idée. Je ne veux pas polluer l'espace de noms global, ce qui m'empêcherait d'instancier deux objets MyClass
différents sans qu'ils interfèrent l'un avec l'autre.
Aucune suggestion? Existe-t-il un moyen propre de faire ce que je recherche? Ou est-ce que tout mon motif est défectueux?
Pour préserver le contexte, la méthode bind
est vraiment utile, elle fait maintenant partie de la récente publication ECMAScript 5th Edition Specification, la mise en oeuvre de cette fonction est simple (8 lignes seulement):
// 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 pourriez 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 deuxième exemple, nous pouvons en observer davantage sur le comportement de bind
.
Il génère essentiellement une nouvelle fonction, qui sera chargée d'appeler notre fonction, en préservant le contexte de la fonction (valeur this
), défini comme le premier argument de bind
.
Le reste des arguments est simplement passé à notre fonction.
Notez dans cet exemple que la fonction fx1
, est invoquée sans aucun contexte object (obj.method()
), comme un simple appel de fonction, dans ce type d'appel, le mot clé this
à l'intérieur fera référence à l'objet Global, il avertira "test global".
Maintenant, le fx2
est la nouvelle fonction générée par la méthode bind
. Elle appellera notre fonction en préservant le contexte et en passant correctement les arguments. Elle alertera "obj test 1, 2, 3, 4, 5" car nous l'avons invoquée en ajoutant le deux arguments en plus, il avait déjà binded les trois premiers.
Pour votre dernier exemple MyClass
, vous pouvez procéder comme suit:
var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
Dans la fonction transmise à each
, this
fait référence à un objet jQuery, comme vous le savez déjà. Si à l'intérieur de cette fonction, vous obtenez la fonction doSomething
à partir de myThis
, puis appelez la méthode apply sur cette fonction avec le tableau d'arguments (voir la fonction apply
et la variable arguments
), alors this
sera défini sur myThis
in doSomething
.
Je réalise que c'est un vieux fil, mais j'ai une solution beaucoup plus élégante et qui présente peu d'inconvénients, mis à part le fait que ce n'est généralement pas le cas, comme je l'ai remarqué.
Considérer ce qui suit:
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é une fonction prototype (test) qui renvoie la variable locale. Comme vous l'avez probablement prévu, lorsque nous créons une instance de cette fonction, puis exécutons la méthode de test, elle ne renvoie pas la variable locale, car lorsque nous avons défini la fonction prototype comme membre de notre fonction principale, elle se trouvait en dehors de la portée. la variable locale est définie . Il s'agit d'un problème général lié à la création de fonctions, puis à l'ajout de prototypes - vous ne pouvez accéder à aucun élément créé dans l'étendue de la fonction principale.
Pour créer des méthodes qui entrent dans la portée de la variable locale, nous devons les définir directement en tant que membres de la fonction et supprimer la référence prototype:
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 craignez peut-être que, du fait que les méthodes ne soient pas créées de manière prototypique, différentes instances ne soient pas réellement séparées des données. Pour démontrer qu'ils le sont, considérons 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ées plus d'une fois. Si vous savez que vous n'aurez jamais besoin d'une seconde instance de la même fonction, il existe un moyen simple de les créer. Soyez averti cependant: lint se plaindra que c'est une construction étrange, et remettez en question 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's: .__ Les avantages de l'utilisation de cette méthode pour créer des objets fonction sont nombreux.
Con's: Il y a quelques inconvénients à utiliser cette méthode. Je ne prétends pas être complet :)
f.constructor.prototype
. Vous pouvez définir la portée en utilisant les fonctions call () et apply ()
Puisque vous utilisez jQuery, notez que this
est déjà géré 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 la li
, alors que y
représente l'enfant span
. Et avec $.click()
, vous pouvez obtenir la portée de l’objet event
:
$("li").click(function(e){
$("span", this).each(function(i,o){
alert(e.target + " " + o);
});
});
Où e.target
représente la li
et o
représente l'enfant span
.
Vous pouvez créer une référence à cet objet ou utiliser la méthode with (this)
. Ce dernier est extrêmement utile lorsque vous utilisez des gestionnaires d’événements et vous n’avez aucun moyen de transmettre une référence.
MyClass = function() {
// More code here ...
}
MyClass.prototype.myfunc = function() {
// Create a reference
var obj = this;
this.element.click(function() {
// "obj" refers to the original class instance
with (this){
// "this" now also refers to the original class instance
}
});
}
Une autre solution (et ma méthode préférée dans jQuery) consiste à utiliser 'e.data' fourni par jQuery pour transmettre 'this'. Ensuite, vous pouvez faire ceci:
this.element.bind('click', this, function(e) {
e.data.myValue; //e.data now references the 'this' that you want
});