Hi,
ich würde gerne eine Memberfunktion der Klasse A an eine andere Klasse
übergeben. Wenn die dann aber das Callback aufrufen soll, gibt es eine
Exception:
TypeError: this.dataUpdateCallback is not a function
Hier ist der Code:
this in einem function Objekt zeigt natürlich auf dieses und nicht auf
das Objekt, aus dem heraus die Funktion erzeugt wurde.
Versuch mal mit
var cbobj = this;
..... function() { cbobj.memberFunc(); }
Damit sollte die richtige Referenz benutzt werden.
Boris P. schrieb:> this.start = function() {> // Do stuff, then call...> this.dataUpdateCallback("Hello World!"); // => TypeError:> this.dataUpdateCallback is not a function
Das this ist ein anderes, als das ausserhalb der function(). Daher musst
Du den äusseren this-Zeiger an die Funktion weiter reichen. Entweder wie
Carl vorschlägt, als Variable im scope, oder z.B. als Parameter:
Danke so weit!
Aber müsste ich dazu nicht wissen, dass "that" eine Funktion
"dataUpdateCallback" hat? Das ist ja nicht unbedingt der Fall.
Klasse A könnte ja eine beliebige Funktion als callback übergeben haben.
Boris P. schrieb:> Danke so weit!>> Aber müsste ich dazu nicht wissen, dass "that" eine Funktion> "dataUpdateCallback" hat? Das ist ja nicht unbedingt der Fall.> Klasse A könnte ja eine beliebige Funktion als callback übergeben haben.
Äh??? Ich zitiere das Beispiel noch mal etwas großräumiger:
1
// Member variables
2
this.dataUpdateCallback = aDataUpdateCallback;
3
4
// Starts the service
5
this.start = function(that) {
6
return function() {
7
// Do stuff, then call...
8
this.dataUpdateCallback("Hello World!");
9
}
10
}(this); // this == that
this.start wird initialisiert mit dem Funktionsaufruf
function(that){...}(this). Diese Funktion liefert eine Funktion, die
keine Parameter mehr nimmt, den this Zeiger aber über den benamten
Parameter that gespeichert hat.
An der Stelle, an der function(that) aufgerufen wird, hat this doch
einen member dataUpdateCallback (den hast Du doch selbst da eingeführt).
Und eben dieses, äußere this wird als Funktionsargument an die Funktion,
die die Funktion zurück liefert übergeben. Jede Funktion in JavaScript
macht einen eigenen Scope auf (this beziehen sich dann immer auf die
lokale Funktion) und kann auf den übergeordneten Scope zugreifen
(closure).
Vielleicht wird es so deutlicher:
Boris P. schrieb:> Hui, es funktioniert :-)> Als C-Denker muss man sich da ganz schön das Hirn verrenken...
Ja, JavaScript ist nix für schwache Nerven! ;-)
Kommando zurück, nix geht ^^
Der Callback wird gemacht, landet aber ganz wo anders!? Die Daten
verschwinden jedenfalls im Nirwana, und landen nicht im Objekt, das den
Service gestartet hat...
Hier mal der echte Code, so wie er nach euren Vorschlägen aussieht:
Du musst innerhalb von this.triggerUpdate() `this` durch `that`
ersetzen. Dass this innerhalb von this.triggerUpdate() ist nicht
dasselbe this, dass die Funktion triggerUpdate() enthält.
Und noch einmal: Jede Funktion in JavaScript hat ihren eigenen this
pointer.
Coffeescript ändert daran leider auch nix. coffeescript ist meiner
Meinung nach aber deutlich besser lesbar.
Torsten R. schrieb:> Du musst innerhalb von this.triggerUpdate() `this` durch `that`> ersetzen. Dass this innerhalb von this.triggerUpdate() ist nicht> dasselbe this, dass die Funktion triggerUpdate() enthält.>> Und noch einmal: Jede Funktion in JavaScript hat ihren eigenen this> pointer.
OK, ich glaube ich habe es verstanden :-)
Ich gebe den this-Pointer rein, und verwende ihn in der Funktion unter
dem Namen that, weil this dort eine andere Bedeutung hat, richtig?
Leider funktioniert es aber immer noch nicht (argh!?):
Boris P. schrieb:> OK, ich glaube ich habe es verstanden :-)
Mal sehen:
> this.start = function() {> this.triggerUpdate(this);
^^^ hier wird dieses this zum ersten mal benutzt, woher sollte
triggerUpdate hier kommen?
> setInterval(function(){ this.triggerUpdate(this); },
^^^^ neue Funktion, neues this
Noch mal! ;-)
Torsten R. schrieb:> Coffeescript ändert daran leider auch nix. coffeescript ist meiner> Meinung nach aber deutlich besser lesbar.
Eben, ein => tippt sich halt bedeutend schneller und liest sich besser.
So, jetzt funktioniert's wirklich :-)
Das Arbeiten mit Klassen ist aber echt ein Krampf in javaScript :-/
TypeScript scheint nach dem, was ich gerade lese, da um einiges
"freundlicher" zu sein. Da gibt es scheinbar auch die this/that
Problematik nicht...
Meinungen dazu?
Das ganze nennt sich "closure"
https://developer.mozilla.org/de/docs/Web/JavaScript/Closures
wenn eine anonyme Funktion via function(){...} erzeugt wird, dann hat
sie Zugriff auf Variablen im aktuellen Scope. Das geht bei JavaScript
nur deshalb, weil jede "Variable" eigentlich nur eine Referenz auf eine
solche ist, die irgendwo auf dem Heap liegt. Zu dem Funktionobject
werden auch die Referenzen auf Variablen außerhalb der Funktion
dazugepackt. Und selbst wenn eine "var" lokal definiert war, bleibt das
Objekt, auf das die lokale "var" Referenz zeigt erhalten, selbst wenn
man den Scope verlässt. Denn im Funktions-Objekt existiert ja noch eine
Referenz. Nur bei this Funktioniert das nicht, denn this im
Funktions-Objekt ist immer das Objekt selbst. Wenn man aber das
"äußeren" this unter neuem Namen ablegt, dann klappt es:
1
// Class A
2
module.exports = function MyTestClass() {
3
4
// Member variables
5
this.service = null;
6
7
// Starts the service
8
this.start = function() {
9
this.service = new MyCoolService(this.processNewData);
10
this.service.start();
11
}
12
13
// Processes incoming data
14
this.processNewData = function(data) {
15
console.log(data);
16
}
17
}
18
19
// Class B
20
module.exports = function MyCoolService(aDataUpdateCallback) {
21
22
// Member variables
23
this.dataUpdateCallback = aDataUpdateCallback;
24
25
var me = this; // "rename" this
26
27
// Starts the service
28
this.start = function() {
29
30
// Do stuff, then call using the MyCoolService object
Boris P. schrieb:> Das Arbeiten mit Klassen ist aber echt ein Krampf in javaScript :-/> TypeScript scheint nach dem, was ich gerade lese, da um einiges> "freundlicher" zu sein. Da gibt es scheinbar auch die this/that> Problematik nicht...
Warum sollte es dass da nicht geben? Soweit ich weiss ist ECMAScript 5
code auch gültiger TypeScript Code. In Funktionen und Classenmethoden
gibt es immernoch ein this und eine Allgemeingültige Referenz auf die
Classeninstanz scheint es nicht zu geben. Ergo bleibt die
this-Problematik erhalten.
TypeScript hat ein par nette features, aber ich denke das ist ein
sinkendes Schiff. Seit ECMAScript 6 gibt es in JavaScript auch Klassen,
damit sind die Typisierung und Interfaces die einzigen gründe TypeScript
weiterzuverwenden. Ausserdem finde ich die Art und Weise wie das
Includieren von Modulen gelöst wurde für sehr unflexiebel und
unpraktisch. Des weiteren kann ich mir sicher sein, das ECMAScript 6 in
den Browsern implementiert wird, und das meine Programme dann Jahrzehnte
lang ohne grössere Anpassungen laufen werden. Bei TypeScript könnte MS
Jederzeit die Weiterentwichlung einstellen oder Änderungen vornehmen,
die zu Inkompatiblitäten führen. Das wurde ja mit den SilverLight und
VB6->VB.net gezeigt.
Es sieht zwar sehr danach aus, aber eigentlich wird eine Membervariable
mit einer Arrow Funktion initialisiert. Im Scope einer Arrow Function
gibt es kein "this", deshalb wird das der darüberliegenden Funktion
verwendet, was in dem fall der Konstruktor ist.
Zur Veranschaulichung:
1
classGreeter{
2
greeting:string;
3
constructor(message:string){
4
this.greeting=message;
5
}
6
greet=()=>{
7
return[
8
message||"?",// <- Das ist eigentlich nicht erlaubt
9
this.greeting||"?",
10
(()=>this.greeting||"?")(),
11
(function(){returnthis.greeting||"?";})()
12
].join(", ");
13
}
14
}
15
16
document.writeln(newGreeter("test").greet());
Ausgabe:
1
test, test, test, ?
Wie man sieht ist der Parameter "message" vom Konstruktor in der
Funktion greet vorhanden, this ist in der Arrow Funktion immernoch
korrekt gesetzt, aber bei einer normalen Funktion immernochnicht. Es
währe viel sinnvoller gewesen, wenn man eine variable "self" eingeführt
hätte, welche in jeder Memberfunktion auf die Instanz zeigt.
Ausserdem ist dies eher eine Einschränkung als ein Feature:
Die Methode nichtmehr im Prototype der Klasse vorhanden, wodurch
TypeScript bei einer Vererbung für diese Methode extracode erzeugen
muss. Dies hat zur Folge, dass dinge wie
Greeter.prototype.great.call({message:"test"}) hier nichtmehr möglich
sind. (Ich verwende das z.B. immer, wenn ich irgendwas Arrayartiges in
ein Array umwandeln muss
Array.prototype.slice.call({length:3,0:1,1:4,2:10}) )
Ausserdem ist es total überflüssig, weil man genausogut derartiges tun
kann:
1
classGreeter{
2
greeting:string;
3
constructor(message:string){
4
this.greeting=message;
5
}
6
greet(){
7
return"Hello "+this.greeting;
8
}
9
}
10
vargreeter=newGreeter("World");
11
varfunc=greeter.greet.bind(greeter);
12
document.writeln(func());// Ausgabe "Hello World"
Nur als vergleich, so würde es in ECMAScript 6 aussehen: