wake-up-neo.com

Einen Verweis auf "this" in JavaScript-Prototypfunktionen beibehalten

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?

88
Jimmy Cuadra

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.

64
CMS

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.

10
icktoofay

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. 

  • Dadurch wird der Code leichter lesbar, da die Methoden eines Funktionsobjekts so eingerückt werden, dass es visuell leichter zu verfolgen ist. 
  • Es ermöglicht den Zugriff auf die lokal definierten Variablennur in ursprünglich auf diese Weise definierten Methoden. Auch wenn Sie später dem Funktionsobjekt prototypische Funktionen oder sogar Elementfunktionen hinzufügen, kann er nicht auf die lokalen Variablen und die gesamte Funktionalität zugreifen oder Daten, die Sie auf dieser Ebene speichern, sind von anderen Stellen aus sicher und unzugänglich.
  • Es ermöglicht eine einfache und unkomplizierte Definition von Singletons.
  • Sie können einen Verweis auf 'this' speichern und auf unbestimmte Zeit beibehalten.

Con's: Es gibt einige Nachteile bei der Verwendung dieser Methode. Ich behaupte nicht, umfassend zu sein :)

  • Da die Methoden als Mitglieder des Objekts und nicht als Prototyp definiert sind, kann die Vererbung mit der Elementdefinition erfolgen, nicht jedoch mit prototypischen Definitionen. Das ist eigentlich falsch. Die gleiche prototypische Vererbung kann erreicht werden, indem auf f.constructor.prototype reagiert wird. 
7
Nemesarial

Sie können den Gültigkeitsbereich mit den Funktionen call () und apply () festlegen.

4
Gabriel McAdams

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.

1
Sampson

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

}
1
Ali C

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
});
0
SimplGy