Allgemeines zu ECMAScript

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.

Kapselung in ECMAScript

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:

  1. Objekte: Ein Objekt in JavaScript ist zunächst ein assoziatives Array. Wenn ich ein Objekt als ein solches Array verwende, verwende ich meist die Syntax:
    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.
  2. Fuktionsscopes: Ein Funktionsscope ist ein Geltungsbereich für Variablen. Alle Variablen, die innerhalb einer Funktion erstellt werden, sind nur innerhalb der jeweilligen Funktion und eventuellen, darin enthaltenen Unterfunktionen sichtbar. Beispiel:
    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.
    Zu beachten ist bei Variablen noch die Vorgezogene Deklaration:
    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.

Klassen in ECMAScript

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!
Zu beachten ist, dass this.v nach außen sichtbar und auch schreibbar ist. Auch können von außen beliebige weitere Eigenschaften (Properties) zum Funktionsobjekt hinzugefügt werden. Eine Erweiterung von ECMAScript, die private properties, oder nicht-überschreibbare Properties erlaubt, ist in Planung, wird aber von JSWindows bislang nicht verwendet.

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:

Objekte freigeben

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.