oopjs
Hier (optional) Anzeige von Autor, Copyrights, Werbung, Links, Session Parametern oder ähnlichem...
oopjs


OOP mit JavaScript

author: evomath

2019-04-21

Diese Testserie nimmt das Verhalten von JavaScript bezüglich des Paradigmas objektorienter Programmierung (OOP) genauer unter die Lupe. Einerseits gibt es einige Überraschungen, wenn man an Java oder C++ gewöhnt ist, andererseits kann eine schwach typisierte Sprache wie JavaScript den Code deutlich verschlanken und eigentlich auch lesbarer machen. Voraussetzung dabei ist allerdings, dass man das Objektkonzept und die Vererbungsmechanismen von JavaScript wirklich verstanden hat und souverän beherrscht. Sonst kann es böse Überraschungen geben.

Zum Debuggen der Beispiele empfehle ich das Mozilla Plugin Firebug und als Browser Firefox. Natürlich sollte man alle Beispiele wenigstens auch in den aktuellen Versionen vom MS Internet Explorer, Chrome, Safari und Opera testen und auf möglichst vielen Betriebssystemen. Als Entwicklungsumgebung benutze ich Eclipse mit Java-, C/C++-, PHP-, XML/XSD/XSLT-, Web-, JavaScript-, LaTEX-, RegEx-, R-Features sowie QuantumDB für MySQL. Als OS Debian7 zum Entwickeln/Server und Windows XP/SP3 bzw. Windows 7 zum Testen mit XAMPP.

In allen folgenden Beispielen wird JavaScript-Code eingebunden (s. unten), ohne den die Beispiele in den Abschnitten nicht funktionieren. Er enthält die Deklarationen und Definitionen der Objekte bzw. Klassen, die in mindestens einem Abschnitt benötigt werden. Zur Zeit werden für die Testbeispiele bewusst keine externen JavaScript Libraries oder Frameworks wie jQuery oder YUI benutzt, sondern es wird nur auf ECMA (Standard ECMA-262, 5th Edition, Dezember 2009) aufgesetzt. Spezifische Ergänzungen, Erweiterungen und Packages sind alle von mir.

Abschnitte

  1. Basisklasse Object und triviale Instanzen inspizieren
  2. Literale Initialisierung von Objekten
  3. Dynamische Initialisierung von Objekten
  4. Methoden in ad hoc Objekten
  5. Die eigene Klasse Rechteck
  6. Vererbung am Beispiel der eigenen Klasse Quader
  7. Vererbungsketten für einige artifizielle Klassen testen
  8. Komplexere verschachtelte Strukturen testen
  9. In ECMA-262 eingebaute Klassen

Appendix

  1. Quellcode
  2. Literatur
  3. Downloads

Klicken Sie auf die orange-farbenen Rechtecke, um die Widgets zur Inspektion der Daten und Meta-Info zu öffnen!

Basisklasse Object und triviale Instanzen inspizieren

So, jetzt geht's los!

Zunächst inspizieren wir mal die in ECMA-262 eingebaute absolute Basisklasse Object und sehen uns an, ob eigene Erweiterungen auch sichtbar sind. Man beachte, dass die von JavaScript vorgefertigten (Object.hasOwnProperty() ist false) Eigenschaften nicht sichtbar sind, weil ja nicht aufzählbar (Object.propertyIsEnumerable() ist für eingebaute Klassen ja false)!

Dann instanziieren wir ein paar triviale Objekte und spielen damit rum. Alle Inspektionen via meiner ergänzten Methode Object.toHtml() bzw. Hilfsfunction meta(), die diese über das als Argument übergebene Object als Methode aufruft. Die Inspektion ist rekursiv, d.h. alle Eigenschaften eines Objektes, die wieder ein Objekt sind werden weiter inspziert, solange bis alles aufgelöst ist bis hin zu primitiven Datentypen.

Interessant in JavaScript ist die Tatsache, dass "Klassen" selbst wieder Objekte sind und die Auswirkungen dieser Selbstbezüglichkeit sind frappant! Man beachte — vorallem die Java-Gurus! — den Aspekt, dass es ja gar keine Klassen gibt, sondern "Klassen" Funktionen/Objekte sind und benutzerdefinierte Erweiterungen der Basis-API-Klassen jederzeit möglich über die prototype Eigenschaft jedes Objektes. Dies kann in Java oder C++ ein echtes Hindernis sein. Wenn man z.B. will, dass alle Objekte eine Methode getId() haben sollen und die über ein statische Eigenschaft Object.counter implementiert wird (wie wohl?), dann erleidet man Schiffbruch: man müsste neue, von Object abgeleitetet Klasse EvoObject o. ä. einführen, was aber die eingebauten, nicht-erweiterten Klassen unberührt lässt. Das ist eine signifikante Begrenzung der Möglichkeiten.

Zur Darstellung werden zum einen meine eigene Hilfsfunktion print(), die ja im wesentlichen nur eine Abkürzung für document.write() ist aufgerufen, die ggfs. das in JavaScript im Prototypen vordefinierte oder später benutzerdefiniert überschriebene Object.toString() aufruft, verwendet. Zum anderen die im Prototypen von Object von mir in oopjs ergänzte Methode Object.toHtml(), die rekursiv ein Objekt hübsch formatiert als interaktiven Tabellen-Baum darstellt (verallgemeinerte, hierarchische Version von Excel!). Abkürzung dafür ist meine Funktion meta(obj) im prozeduralen Stil. Die ist auch im module oopjs definiert.

meta(Object);
meta(new Object);

Literale Initialisierung von Objekten

Mal gucken, ob direkt literal initialisierte Objekte funktionieren. In diesem Beispiel ist das Objekt dieZahlZwei bereits global im Testcode vordefiniert zum Testen. Das ist natürlich sehr schlechter Stil und sollte undedingt vermieden werden in realen Anwendungsfällen, wird hier aber bewusst gemacht, um das Wesentliche im Auge zu behalten. JavaScript Patterns sind ein eigenes Thema, das später behandelt wird.

print(dieZahlZwei);
meta(dieZahlZwei);
print(dieZahlZwei.text());

Dynamische Initialisierung von Objekten

Als assoziatives Array erzeugen per Schleife und dann als Objekt behandeln zum Aufruf der Eigenschaften.

// zufälliger Obsteinkauf
var MAX_MENGE = 10;
var obst = ["apfel", "banane", "birne", "kirsche", "pfirsich"];
var einkauf = {};  // leeres, literales Objekt; statt `new Object()`;
for (var i = 0, n = obst.length; i < n; i++) {
	einkauf[obst[i]] = Math.round(MAX_MENGE * Math.random());
}
println(einkauf.apfel);
println(einkauf.banane);
println(einkauf.birne);
println(einkauf.kirsche);
println(einkauf.pfirsich);
meta(einkauf);

Methoden in ad hoc Objekten

Mal gucken, ob "Methoden" in Objekten funktionieren:

meta(rechtwinkligesDreieck);
println(rechtwinkligesDreieck.text(3, 4));
print(rechtwinkligesDreieck.text(1, 1));

Die eigene Klasse Rechteck

Die eigene Klasse Rechteck zuerst mit leerem Konstruktor und dreimal mit allen Argumenten instanziieren und eine Methode aufrufen:

Beachten Sie, dass 1. der Konstruktor die Argumente auf undefined prüft und so auch den leeren (Standard-)Konstruktor erlaubt, dabei aber 2. wegen der Initialisierungsliste der Felder (declare and init the members) trotzden sinnvolle default Werte hat, nämlich 0!

var rechteck0 = new Rechteck();
var rechteck1 = new Rechteck(3,4);
var rechteck2 = new Rechteck(5,6);
var rechteck3 = new Rechteck(7,8);
meta(rechteck0);
meta(rechteck1);
meta(rechteck2);
meta(rechteck3);
println(rechteck0.text());
println(rechteck1.text());
println(rechteck2.text());
print(rechteck3.text());

Vererbung am Beispiel der eigenen Klasse Quader

Objekt der Klasse Quader, die ja von Rechteck abgeleitet ist, instanziieren. Dann gucken, ob sich Eigenschaften und Methoden richtig vererben und (Pseudo-)"Polymorphismus" greift (hier am Beispiel der Methode text(), die ja sowohl in Klasse Rechteck wie auch in Quader vorkommt, also "konkret virtuell" in der Basisklasse ist und "überschrieben" wird in der abgeleiteten Klasse):

Beobachten Sie, wie sich Felder und Methoden von Rechteck vererben. Hierbei wird der universelle Konstruktor mit 3 Argumenten aufgerufen

var quader = new Quader(2,3,4);
meta(quader);
print(quader.text());

Vererbungsketten für einige artifizielle Klassen testen

Ein paar Objekte der artifiziellen Klassen erzeugen und sehen, was dabei rauskomt. Dann die veschiedenen OOP-Mechanismen gründlich testen und kombinieren.

Ich wollte den (argumentlosen) Standardkonstruktor überladen und also 2 Konstruktoren bereitstellen -- Das geht schief, vermutlich weil er ja nicht zwischen leerem Argument und undefined unterscheiden kann, dann die letzte auftretende Deklaration/Definition des Konstruktors als Prototyp übernimmt. Schade!

Ausweg: Argumente checken in dem einen und nur einen "Universalkonstruktor" und in Abh.davon dann verschieden initialisieren.

var a = new A();
println(A.nameOfClass()); 
println(a.nameOfClass()); 
println(a.nameOfSuper());
println(a.f());
meta(A); // Klasse inspizieren
meta(a); // Objekt inspizieren
var a1 = new A(1);
println(a1.nameOfClass());
println(a1.nameOfSuper());
println(a1.f());
meta(a1);
var b = new B();
println(b.nameOfClass());
println(b.nameOfSuper());
println(b.g());
meta(b);
var b12 = new B(1,2);
println(b12.nameOfClass());
println(b12.nameOfSuper());
println(b12.g());
meta(b12);
var c = new C();
println(c.nameOfClass());
println(c.nameOfSuper());
println(c.h());
meta(c);
var c123 = new C(1,2,3);
println(c123.nameOfClass());
println(c123.nameOfSuper());
println(c123.h());
meta(c123);

Komplexere verschachtelte Strukturen testen

Komplexere, verschachtelte Objekte literal erzeugen; rekursiv inspizieren; Referenzen und Methoden testen:

Das erste Objekt z ist artifiziell und enthält sogar Funktionen und so gut wie alle Datentypen. Zuerst wird z erst einmal vollständig inspiziert (mit Helper meta()), dann nur der Inhalt, also die Daten (mit Helper data()).

Das zweite "Business"-Object biz hat mehr Realitätsbezug wird ebenfalls literal erzeugt, aber der Quellcode aus Platzgründen verschoben in das extern eingebundene Skript. Hier wird nicht meta(), sondern nur data() zum Inspizieren verwendet, weil das einfach und übersichtlich wirken soll wie in einer realen Anwendung a la Excel, nur verschachtelt eben. Da sind Metadaten überflüssig. Klar, dass da bald JSON und XML via SQL und serverseitiger Datenbank ins Spiel kommen!

Das dritte Objekt ist recht gross. Das Beispiel kommt aus der YUI2 library examples für Klasse DataTable. Es wird mit data() inspiziert. Nicht erschrecken: Ziemlich viel Information drin!

z = { e:2.718, obj1:{ x:2, f:function() { return this.x * this.x; }, nul:null }, 
  p:17, str:'hallo, Ihr da!', obj2: { undef:undefined, bool:true, 
  arr:['null','eins','zwei','drei'], obj:{one:1, two:2} } };
meta(z);
data(z);
data(biz);
data(dat);

In ECMA-262 eingebaute Klassen

In ECMA eingebaute Klassen rekursiv inspizieren; Referenzen und Methoden testen:

var date = new Date();
println(typeof date.constructor);
println(date.constructor);
println(date instanceof Date);
println(date.constructor == 'Date');
println(date.constructor.name); 
println(date.constructor.toName()); 
meta(date);
var array = new Array(1,2,3);
meta(array);