ECMAScript oder auch besser bekannt als JavaScript ist die Hauptgrundlage von JSWindows, das seinen Namen teilwese von JavaScript erbt. In dieser Dokumentation verwende ich jedoch die "offizielle" Bezeichnung ECMAScript, da zwar gewisse syntaktische Ähnlichkeiten zu der Programmiersprache Java existieren, aber sowohl der Ursprung, Anwendungsbereiche, als auch der Aufbau beider Sprachen sind sehr verschieden und es wäre falsch, JavaScript als eine Scriptversion von Java zu betrachten.
ECMAScript ist eine prototypische Sprache - das heißt es ist nicht wirklich, wie Java, Objektorientiert, erlaubt aber trotzdem eine Programmierung, wie man sie von objektorientierten Sprachen her kennt. Es gibt dabei 2 wichtige, verschiedene Arten, Funktionen und Variablen zu kapseln:
var o = new Object();
o['eigenschaft1'] = 'Hallo';
o['eigenschaft2'] = 'Welt';
Es wäre aber genausogut möglich, o.eigenschaft1 zu schreiben. Diese Notation verwende ich vornehmlich, wenn ich Objekte als Klassen verwende.
var v = 'Hallo';
function f () {
var v = 'Welt';
alert(v); // alerts 'Welt'
window.setTimeout(function () { alert(v); /* alerts 'Welt' */ }, 10000);
}
alert(v); // alerts 'Hallo'
Bemerkenswert ist, dass auch die verzögert aufgerufene Funktion im Timeout Zugriff auf die innere v-Variable hat. Das Scope bleibt also nach Beendigung der Funktion erhalten,
falls in dieser Funktion deklarierte Unterfunktionen ggf. später noch aufgerufen werden. Das gilt auch für Eventlistener.function f () {
var v;
v = 'Hallo Welt';
alert(v);
}
function g () {
v = 'Hallo Welt';
alert(v);
var v;
}
Der Code in beiden Funktionen ist hier äquivalent. Ich vermeide aber die Verwendung von Variablen vor ihrer eigentlichen Deklaration. Später werden sich Funktionsscope und
Objekt vermischen. Eine fehlende Variablendeklaration würde die Variable als Property des Funktionsobjektes erstellen, anstatt als Variable des Scopes.
ECMAScript unterstützt nativ keine Klassen. Um trotzdem eine vergleichbare Struktur zu bekommen, ist es üblich, eine Funktion als Objekt zu erstellen:
function DieKlasse () {
var v = 'Hallo';
this.v = 'Welt';
alert(v + ' ' + this.v); // alerts 'Hallo Welt'
}
var instanz = new DieKlasse();
alert(instanz.v); // alerts 'Welt'
Wird eine Funktion mit new aufgerufen, so wird neben dem Scope außerdem das Funktionsobjekt this erstellt. Die Verwendung von gleichen Bezeichnern für
Scope- und Funktionsobejktvariablen, wie oben, sollte im richtigen Code unbedingt vermieden werden!Neben dem Funktionsobjekt gibt es noch den Prototype. Beispiel:
function DieKlasse () {
this.v = 'Hallo';
alert(this.v + ' ' + this.w); // alerts 'Hallo Welt'
this.w = 'Welt2'; // hier wird eine neue Eigenschaft this.w im Funktionsobjekt erstellt, die DieKlasse.prototype.w ueberdeckt; DieKlasse.prototype.w bleibt aber unveraendert.
}
DieKlasse.prototype.w = 'Welt';
var instanz = new DieKlasse();
Nach der Instanziierung werden Prototype und Funktionsobjekt zusammengelegt. Eigenschaften des Prototypes können dabei überschrieben, oder besser "überdeckt" werden;
in einer neuen Instanz wäre aber wieder der ursprüngliche Wert des Prototypes vorhanden.
Ein Vorteil von Prototype ist, dass man eine Art von Vererbung herstellen kann:
function DieKlasse () {
var p = ' Welt'; // Achtung: Die Variable p ist nicht im Konstruktor von DieUnterklasse verfuegbar!
this.v += p; // Achtung: Die Variable v ist nicht Eigenschaft des Prototypes und in DieUnterklasse also erst nach dem inherited-Aufruf von DieKlasse() verfügbar!
}
DieKlasse.prototype.v = 'Hallo';
function DieUnterklasse () {
DieKlasse.call(this); // inherited
alert(this.v + this.w); // alerts 'Hallo Welt!!!':
}
DieKlassePrototype = function () { }
DieKlassePrototype.prototype = DieKlasse.prototype;
DieUnterklasse.prototype = new DieKlassePrototype();
DieUnterklasse.prototype._super = DieKlasse.prototype;
DieUnterklasse.prototype.w = '!!!'; // Erweitert den Prototyp von DieUnterklasse, aber nicht den Prototyp von DieKlasse
var instanz = new DieUnterklasse();
Die Verwendung von Scopevariablen ist hier Problematisch, da Funktionen, die als Eigenschaften von Prototype deklariert werden, oder zu einer Unterklasse gehören,
keinen Zugriff auf die Scopevariablen haben. Alle Klassenvariablen müssen bei diesem Konzept also Eigenschaften des Prototypes oder des Funktionsobjekts sein.
Unterklassen können Funktionen (und Eigenschaften) der Vaterklasse überschreiben. Wenn der Pototype wie oben mit Hilfe eines Zwischenobjekts DieKlassePrototype
vererbt wird, bleibt der Prototype von DieKlasse unverändert und kann zum Beispiel in Form eines _super-Properties für inherited-Aufrufe weiter verwendet werden.
Im Hinblick auf die oben erläuterten Techniken verwende ich zwei verschiedene Grundtypen von Klassen, welche ich Scopeklasse und Prototypeklasse nenne:
Scopeklasse: Falls eine Klasse nur einmal oder einige, wenige Male instanziiert wird und keine Vererbung benötigt wird, verwende ich das folgende Konstrukt:
function Scopeklasse () { with (this) {
// private
var v1 = 1;
var v2 = 2;
// public
this.v3 = 3;
this.v4 = 4;
var f1 = /* private */ function (p1) {
alert(v1 + v3 + p1);
}
this.f2 = /* public */ function (p1) {
alert(v2 + v4 + p1);
}
} }
Hierbei werden alle Eigenschaften und Funktionen im Konstruktor deklariert. Dadurch ist dessen Scope überall verfügbar und kann für private Eigenschaften und
Funktionen verwendet werden. Per with (this) { ... } werden Funktionsobjekt und -scope zusammengelegt und private-, sowie public-Eigenschaften können
direkt und ohne Unterschiede genutzt werden.
Prototypeklasse: Für Klassen, die schnell erstellt werden müssen, oder Vererbbar sein sollen, verwende ich Prototypes:
function Prototypeklasse () {
// private
this.v1 = 1;
// public
this.v3 = 3;
}
Prototypeklasse.prototype.v2 = 2;
Prototypeklasse.prototype.v4 = 4;
Prototypeklasse.prototype.f1 = /* private */ function (p1) {
alert(this.v1 + this.v3 + p1);
}
Prototypeklasse.prototype.f2 = /* public */ function (p1) {
alert(this.v2 + this.v4 + p1);
}
Hierbei wird auf var-Variablen (Scopevariablen) als Klassenvariablen verzichtet. Sie treten nur noch als lokale Variablen auf. Zwar erfolgt die Unterscheidung zwischen
private und public nur noch per Kommentar und es muss überall this vor der Verwendung einer Eigenschaft stehen, aber dafür ist diese Klasse vererbbar
und die prototype-Eigenschaften und -Funktionen müssen nur noch ein mal beim laden der Seite erzeugt werden, nicht aber bei jedem Aufruf des Konstruktors.
Die Unterscheidung zwischen private und public erfolgt per Kommentar. Zusätzlich verwende ich folgende Deklarationen:
Nach viel Überlegens wurde der Entschluss gefasst, dass JSW-Objekte freigegeben werden können müssen. ECMA-Script selbst kennt keine Destruktoren oder aktives Freigeben von Objekten. Objekte werden automatisch vom Grabbage Collector eingesammelt, sobald keine Referenz mehr darauf existiert. Aber gerade bei JS Windows kann dies problematisch sein, da Fenster, Manager, usw. sehr komplex verschachtelt sein können, dazu kommen HTML-Elemente, die über kaum ersichtliche EventListener Referenzen auf Objekte speichern können.
Daher gibt es in JS Windows die Möglichkeit, Objekte aktiv über eine free-Methode freigeben zu können. Dadurch werden alle EventListener gelöscht, HTML-Elemente des entsprechenden Objekts ausgehängt und dann alle Properties des Objekts vom Typ Objekt auf null gesetzt. In den meisten JSW-Objekten existiert eine Eigenschaft freed, diese wird in free auf true gesetzt, Funktionen des freigegebenen Objektes sollen dadurch ein entsprechendes Exception (Object is freed.) werfen. Mit diesem Trick ist es während der Entwicklung möglich, einfacher "Speicherleichen" zu finden, also Fenster, auf die durch irgend einen vergessenen EventListener noch eine Referenz existiert.
Fenster (JSWindow) haben zusätzlich die Eigenschaft freeOnHide, dadurch werden sie nach dem Schließen automatisch freigegeben.