Objektorientiertes Programmieren ist in JS für gestandene OOPler etwas „komplizierter“. Das Member-Methoden ihren Bezug zum Objekt verlieren können ist etwas gewöhnungsbedürftig. Auch sonst ist das Thema der Sichtbarkeiten unter JS etwas gewöhnungsbedürftig. Man hat nun zwei Möglichkeiten:
- Lernen durch Schmerzen – Trial and Error: das kann für eingefleischte OOP-ler sehr schmerzvoll werden
- Sich über die Doku einarbeiten
Leider wird letzteres sehr schwer. Die mir bekannten Dokumentation schwanken stark zwischen „völlig belanglos“, „für Anfänger“ bis „am Thema vorbei“. Es gibt nur wenige Dokumente die das Konzept hinter JS klar darlegen. Manch ein Kritiker unterstellt das es so was bei JS nicht gibt 😉
Am besten stellt sich das OOP-Konzept von JS bei einem Vergleich dar. HIer eine Klasse im klassischen OOP alla Java, C++ oder C-Sharp
public class MyClass
{
private string someVariable;
public class MyClass(string someVariable)
{
this.someVariable=someVariable;
}
public string GetVariable()
{
return someVariable;
}
}
Nun die Klasse in JS
function MyClass(someVariable){
this.someVariable=someVariable;
}
MyClass.prototype.AlertVariable=function(){
alert(this.someVariable);
}
Das erste was auffällt ist, dass es kein Klassen-Konstrukt wie unter Java oder CSharp gibt. Eine Klasse wird über ihren Konstruktor repräsentiert. Will man Methoden hinzufügen, so geschieht dies über den prototype. Soweit so normal und verständlich. Kommen wir nun zu den interessanten Sachen.
var myInstanz=new MyClass("SomeValue");
myInstanz.AlertVariable();
$(document).ready(myInstanz.AlertVariable);
Wer den Term „$(document)“ irritierend findet, sollte sich vorher noch mal die jQuery-Tutorials anschauen. Der normale OOP-ler würde (so er denn die Syntax versteht) erwarten, dass nun zwei mal „SomeValue“ ausgegeben wird. Führt man den JavaScript aus, kommt es nur zu einer Meldung und einem Fehlersymbol (je nach Browser). Führt man den Code im Debugger aus stellt sich schnell die Zeile „return this.someVariable;“als Fehlerquelle raus. Beim „zweiten“ Aufruf innerhalb des Events „zeigt“ der this-Operator nicht mehr auf die Instanz der Klasse sondern auf den Event-Auslöser.
Nun kann man sich einen Knoten ins Hirn machen und alles gelernte über Bord werfen und wieder „schön“ Prozedural programmieren. Damit umgeht man einen Großteil der Probleme. Es wird aber sehr schnell sehr unübersichtlich. Eine weitere Möglichkeit ist, zu tricksen. Das grundlegende Problem mit dem this-Operator lässt sich nicht lösen oder umgehen. Der man kann es nur mit anderen Strukturen „umschiffen“. Das Beispiel von Oben ein wenig umgestellt.
var myInstanz=new MyClass("SomeValue");
myInstanz.AlertVariable();
$(document).ready(function (){
myInstanz.AlertVariable();
});
Da jetzt die Instanz der Klasse direkt angesprochen wird, funktioniert intern wieder der this-Operator wie gewohnt. Was dem verwirrten OOP-ler jetzt vollends aus der Bahn werfen dürfte, Warum zum Geier kann man aus der Subfunktion auf die Variable myInstanz zugreifen. Aber diesen Umstand verbucht man unter „nicht Fragen nur Wundern“. Leider lässt sich nicht jedes Problem so übersichtlich lösen, aber das Prinzip bleibt immer das gleiche: Der Event-Handler, der den this-Operator verbiegt wird in einem Kontext erstellt, wo die Referenz auf die Zielinstanz noch vorhanden ist (oder man stellt die Referenz her) und greift dann über die Sichtbarkeit auf diese Referenz zu. Mal ein komplexeres Problem.
function MyClass(someDomObject,displayText){
this.alertVariable=function(){
alert(displayText);
};
var alertVariable=this.alertVariable;
$("a",someDomObject).each(function(){
$(this).click(function(){
alertVariable();
});
});
}
MyClass.prototype.AlertVariable=function(){
this.alertVariable();
}
Ok was wird hier getrieben. Der Klasse MyClass wird ein DOM-Object übergeben. Dieses Objekt kann alles sein, auch eine XML-Node, Hauptsache es ist von jQuery verarbeitbar. Der Ausdruck $(„a“,someDomObject) selektiert jedes a-Element (<a href=““ />) und mittels der each-Funktion wird für jedes selektierte Objekt die übergebene Funktion (die inplace gebaut wird) ausgeführt. Für die ich jetzt abgehängt hab, das ganze in C-Sharp. Das würde dann so aussehen (mit LINQ).
public class MyClass
{
private string someVariable;
public class MyClass(XmlNode someDomObject, string displayText)
{
var selectedNodes = someDomObject.SelectNodes("a");
//LINQ & Delegate
selectedNodes.ForEach(DoSomething)
//klassisch
foreach(XmlNode node in selectedNodes)
{
DoSomething(node);
}
}
private void DoSomething(XmlNode node)
{
//Die XmlNode-Klasse hat in .NET kein Click(ed)-Event ich spar mir das mal
}
public void AlertVariable()
{
MessageBox.Show(someVariable);
}
}
Wirklich „brainfucked“ ist dann die „Zeile“: $(this).click(function() {}); Der this-Operator zeigt dabei nicht mehr auf die Klasse, wie man beim ersten lesen vielleicht vermuten würde, sondern „konsequenterweise“ auf das abzuarbeitende Element. In CSharp entspricht das dem Übergabewert der Funktion DoSomething. Wem sich das Konzept mal eröffnet hat, wird schnell feststellen, dass die Sichtbarkeiten wie in C/C++ gelöst wurden (einschließlich globaler Objekte). Nur dieser this-Operator ist halt ein wenig frickelig. Deswegen sieht dieser Block so „scheiße“ aus:
this.alertVariable=function(){
alert(displayText);
};
var alertVariable=this.alertVariable;
Erst wird eine Funktion/Delegetate auf eine Funktion angelegt. Diese wird in der der Member-Variable „alertVariable“ referenziert. Auf diese kann aber innerhalb der Each->Click funktion nicht zugegriffen werden. Da der this-Operator ja umgebogen wird. Daher kopiert man die Referenz in eine lokale Variable, die wiederum ist überall verfügbar…
Wenn man diesen Spaß auf die Spitze treiben will, schreibt mal einfach sechs bis sieben Ebenen wo jeder this-Operator auf ein anderes Ziel zeigt (z.B. ein XmlDokument mit n-Ebenen die alle einzeln abgearbeitet werden müssen;)). Dass ist dann Brainfuck-delux.