Ich bin gerade damit beschäftigt, prototypisches JavaScript zu verwenden, und ich habe Schwierigkeiten, herauszufinden, wie eine this
-Referenz auf das Hauptobjekt innerhalb einer Prototypfunktion beibehalten wird, wenn sich der Geltungsbereich ändert. Lassen Sie mich erläutern, was ich meine (ich verwende hier jQuery):
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();
Ich weiß, dass ich einen Verweis auf das Hauptobjekt beibehalten kann, indem Sie dies am Anfang von myfunc
tun:
var myThis = this;
und dann mit myThis.myValue
auf die Eigenschaft des Hauptobjekts zugreifen. Aber was passiert, wenn ich eine ganze Reihe von Prototypfunktionen für MyClass
habe? Muss ich den Verweis auf this
am Anfang eines jeden speichern? Es scheint, als sollte es einen saubereren Weg geben. Und was ist mit einer Situation wie dieser:
MyClass = function() {
this.elements $('.elements');
this.myValue = 'something';
this.elements.each(this.doSomething);
}
MyClass.prototype.doSomething = function() {
// operate on the element
}
new MyClass();
In diesem Fall kann ich mit var myThis = this;
keinen Verweis auf das Hauptobjekt erstellen, da selbst der ursprüngliche Wert von this
im Kontext von doSomething
ein jQuery
-Objekt und kein MyClass
-Objekt ist.
Es wurde mir vorgeschlagen, eine globale Variable zu verwenden, um den Verweis auf die ursprüngliche this
zu speichern, aber das scheint mir eine wirklich schlechte Idee zu sein. Ich möchte den globalen Namensraum nicht verschmutzen, und das scheint mir zu verhindern, dass zwei verschiedene MyClass
-Objekte instanziiert werden, ohne dass sich diese gegenseitig beeinflussen.
Irgendwelche Vorschläge? Gibt es eine saubere Möglichkeit, das zu tun, was ich suche? Oder ist mein gesamtes Designmuster fehlerhaft?
Um den Kontext zu erhalten, ist die bind
-Methode sehr nützlich. Sie ist jetzt Teil der kürzlich veröffentlichten ECMAScript 5th Edition Spezifikation. Die Implementierung dieser Funktion ist einfach (nur 8 Zeilen lang):
// 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)));
};
};
}
Und Sie könnten es in Ihrem Beispiel so verwenden:
MyClass.prototype.myfunc = function() {
this.element.click((function() {
// ...
}).bind(this));
};
Ein anderes Beispiel:
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);
In diesem zweiten Beispiel können wir mehr über das Verhalten von bind
beobachten.
Es generiert im Grunde eine neue Funktion, die dafür verantwortlich ist, unsere Funktion aufzurufen, wobei der Funktionskontext (this
value), der als erstes Argument von bind
definiert ist, erhalten bleibt.
Die restlichen Argumente werden einfach an unsere Funktion übergeben.
Beachten Sie in diesem Beispiel, dass die Funktion fx1
ohne Objektkontext (obj.method()
) aufgerufen wird. Ebenso wie ein einfacher Funktionsaufruf. Bei diesem Aufruftyp bezieht sich das this
-Schlüsselwort auf das globale Objekt wird "global test" alarmieren.
Nun ist fx2
die neue Funktion, die von der bind
-Methode generiert wurde. Sie ruft unsere Funktion auf, wobei der Kontext beibehalten und die Argumente korrekt übergeben wird. Sie gibt "obj test 1, 2, 3, 4, 5" aus, da wir sie beim Hinzufügen von zwei zusätzliche Argumente, es hatte bereits gebunden die ersten drei.
Für Ihr letztes MyClass
-Beispiel könnten Sie Folgendes tun:
var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });
In der Funktion, die an each
übergeben wird, verweist this
auf ein jQuery-Objekt, wie Sie bereits wissen. Wenn Sie innerhalb dieser Funktion die doSomething
-Funktion von myThis
erhalten und dann die apply-Methode für diese Funktion mit dem arguments-Array aufrufen (siehe apply
function und arguments
variable ), wird this
auf gesetzt myThis
in doSomething
.
Mir ist klar, dass dies ein alter Faden ist, aber ich habe eine Lösung, die viel eleganter ist und nur wenige Nachteile hat, abgesehen davon, dass dies im Allgemeinen nicht geschieht, wie ich bemerkt habe.
Folgendes berücksichtigen:
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
In der Funktion oben haben wir eine lokale Variable (Kontext) definiert. Wir haben dann eine prototypische Funktion (Test) hinzugefügt, die die lokale Variable zurückgibt. Wie Sie wahrscheinlich vorhergesagt haben, gibt die lokale Methode keine Variable zurück, wenn wir eine Instanz dieser Funktion erstellen und dann die Testmethode ausführen. Wenn wir die prototypische Funktion als Member unserer Hauptfunktion definiert haben, war es außerhalb des Bereichs, in dem die Lokale Variable ist definiert .. Dies ist ein allgemeines Problem beim Erstellen von Funktionen und beim Hinzufügen von Prototypen - Sie können nicht auf alles zugreifen, was im Rahmen der Hauptfunktion erstellt wurde.
Um Methoden zu erstellen, die im Gültigkeitsbereich der lokalen Variablen liegen, müssen wir sie direkt als Mitglieder der Funktion definieren und die prototypische Referenz loswerden:
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;
Möglicherweise machen Sie sich Sorgen, dass unterschiedliche Instanzen möglicherweise nicht durch Daten getrennt werden, da die Methoden nicht prototypisch erstellt werden. Um dies zu beweisen, bedenken Sie folgendes:
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
Eine andere Möglichkeit, diese Methode zu verwenden, ist das Erstellen von Singletons. Meist werden unsere Javascript-Funktionen nur einmal instanziiert. Wenn Sie wissen, dass Sie niemals eine zweite Instanz derselben Funktion benötigen, gibt es eine Kurzform, um sie zu erstellen. Seien Sie jedoch gewarnt: lint wird sich darüber beschweren, dass es sich um eine seltsame Konstruktion handelt, und Ihre Verwendung des Keywords "new" in Frage stellen:
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: Die Vorteile dieser Methode zum Erstellen von Funktionsobjekten sind reichlich.
Con's: Es gibt einige Nachteile bei der Verwendung dieser Methode. Ich behaupte nicht, umfassend zu sein :)
f.constructor.prototype
reagiert wird. Sie können den Gültigkeitsbereich mit den Funktionen call () und apply () festlegen.
Da Sie jQuery verwenden, ist es erwähnenswert, dass this
bereits von jQuery selbst verwaltet wird:
$("li").each(function(j,o){
$("span", o).each(function(x,y){
alert(o + " " + y);
});
});
In diesem Beispiel steht o
für die li
, während y
für das untergeordnete span
steht. Und mit $.click()
können Sie den Gültigkeitsbereich aus dem event
-Objekt abrufen:
$("li").click(function(e){
$("span", this).each(function(i,o){
alert(e.target + " " + o);
});
});
Dabei steht e.target
für die li
und o
für das untergeordnete span
.
Sie können einen Verweis auf dieses Objekt erstellen oder die with (this)
-Methode verwenden. Letzteres ist äußerst nützlich, wenn Sie Eventhandler verwenden und keine Möglichkeit haben, eine Referenz zu übergeben.
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
}
});
}
Eine andere Lösung (und meine bevorzugte Methode in jQuery) besteht darin, die von jQuery bereitgestellte 'e.data' zu verwenden, um 'this' zu übergeben. Dann können Sie das tun:
this.element.bind('click', this, function(e) {
e.data.myValue; //e.data now references the 'this' that you want
});