2008-10-08 4 views
58

Ich habe Probleme mit einfachen alten JavaScript (keine Frameworks) in Bezug auf mein Objekt in einer Callback-Funktion.JavaScript Callback Scope

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

Wenn ich jetzt erstellen Sie ein neues Objekt (nach dem DOM geladen ist, mit einer Spannweite # test)

var x = new foo('test'); 

Die ‚this‘ in den Onclick-Funktion zeigt auf die Spanne # Test und nicht das foo-Objekt.

Wie bekomme ich einen Verweis auf mein foo-Objekt innerhalb der Onclick-Funktion?

Antwort

81

(extrahiert eine Erklärung, die in den Kommentaren in anderen Antwort versteckt war)

Das Problem in der folgenden Zeile liegt:

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

Hier passieren Sie ein Funktionsobjekt als Rückruf verwendet werden soll . Wenn das Ereignis ausgelöst wird, wird die Funktion aufgerufen, aber jetzt hat sie keine Verknüpfung mit einem Objekt (this).

Das Problem kann wie folgt durch Umwickeln die Funktion (mit seiner Objektreferenz) in einem Verschluss gelöst werden:

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

Da die Variable selbst diese, wenn der Verschluß angelegt wurde, wobei die Verschluss-Funktion zugewiesen wurde wird sich den Wert der Eigenvariablen merken, wenn sie zu einem späteren Zeitpunkt aufgerufen wird.

Eine alternative Möglichkeit, dies zu lösen, ist eine Nutzenfunktion zu machen (und vermeiden Variablen diese zur Bindung):

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

Der aktualisierte Code würde wie dann aussehen:

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

Function.prototype.bind ist Teil von ECMAScript 5 und bietet die gleiche Funktionalität. So können Sie tun:

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

Für Browser, die ES5 noch nicht unterstützen, MDN provides the following shim:

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

Die Dienstprogrammfunktion createCallback ist praktisch! Vielen Dank. –

+0

closure() ist wahrscheinlich ein besserer Name. – hishadow

+2

Oder bind() oder bindMethod(). – outis

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

Dank verwenden, aber können Sie erklären, warum ich dort eine anonyme Funktion erstellen müssen? Warum ändert das die Bindung? –

+0

Immer wenn ein Ereignis ausgelöst wird, bezieht sich "dies" auf das Objekt, das das Ereignis aufgerufen hat. – Tom

+2

Wenn Sie das Formular in Ihrer Frage verwenden, ist self.onclick nur eine anonyme Funktion, die bei der Bearbeitung so funktioniert, wie Sie sie direkt an den Bereich angefügt haben. Wenn Sie es in eine Schließung umschließen, macht self.onclick (event) wirklich das, was Sie wollen, z. verwendet 'selbst' aus dem Kontext, der den Abschluss definiert. –

-4

dies ist einer der verwirrendsten Punkte von JS: die ‚this‘ Variable auf den meisten lokalen Objekt bedeutet ... aber Funktionen sind auch Objekte, so ‚dieses‘ es Punkte. Es gibt andere subtile Punkte, aber ich erinnere mich nicht an alle.

Ich vermeide es normalerweise, 'this' zu verwenden, sondern definiere einfach eine lokale 'me' Variable und verwende diese stattdessen.

+2

Ihre Aussage ist völlig ungenau. Mit den Methoden apply() und call() einer Funktion können Sie eine Funktion ausführen, wobei 'this' sich auf eine Variable bezieht, die Sie im Aufruf übergeben. – Kenaniah

+1

@Kenaniah: .... was zur Verwirrung beiträgt, die genaue Bedeutung des Codes hängt davon ab, wie er heißt! – Javier

+0

Funktionen sind sehr flexibel in JavaScript und nicht mit 'this' ist kaum eine Lösung für das Problem. 'this' ist das einzige dynamische Scoping-Feature in JS und verleiht der Sprache eine Menge Power. Und ja, die Tatsache, dass Funktionen erstklassige Objekte in JS sind, impliziert die dynamische Natur des Ausführungskontextzeigers. Es ist ein großartiges Feature, wenn es sinnvoll eingesetzt wird. –

2

Die Erklärung dafür ist, dass self.onclick bedeutet nicht, was Sie denken, es bedeutet, in JavaScript. Es bedeutet eigentlich die onclick Funktion im Prototyp des Objekts self (ohne in irgendeiner Weise referenziert self selbst).

JavaScript hat nur Funktionen und keine Delegaten wie C#, daher ist es nicht möglich, eine Methode AND das Objekt zu übergeben, auf das es als Callback angewendet werden soll.

Die einzige Möglichkeit, eine Methode in einem Callback aufzurufen, besteht darin, sie in einer Callback-Funktion selbst aufzurufen. Da JavaScript-Funktionen Verschlüsse sind, sind sie in der Lage, die Variablen im Gültigkeitsbereich deklariert für den Zugriff auf sie erstellt wurden.

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

Ich rief die Prototyp-Funktion 'onclick' an, um sie einfach zuzuordnen. Ich wusste, dass es nicht automatisch vom eigentlichen DOM onclick-Ereignis aufgerufen würde, weshalb ich versuchte, die Ereignis-Listener dazu zu bringen, den onclick meines Objekts mit der onclick-Funktion des DOM zu verbinden. –

+0

Das war nicht mein Punkt, ich verstehe Ihre Onclick-Funktion. Mein Punkt ist, dass es in JavaScript keinen Unterschied zwischen self.onclick und foo.prototype.onclick gibt. In JavaScript gibt es keine Möglichkeit, "diese Methode an dieses Objekt gebunden" zu sagen. –

+0

Der einzige Weg ist, einen Verschluss zu verwenden. – dolmen

1

ich dieses Plugin geschrieben ...

Ich denke, es nützlich sein wird

jquery.callback

2

Eine gute Erklärung des Problems (ich hatte Probleme, die bisher beschriebenen Lösungen zu verstehen) ist available here.

5

Für die jQuery Benutzer für eine Lösung für dieses Problem suchen, sollten Sie jQuery.proxy

+0

Dies ist eine nette Lösung, da sie direkt in jQuery integriert ist. – Chetan