Sie sind auf Seite 1von 38

Objektorientierte Programmierung mit JavaScript 1.5

Unister GmbH, Janko Richter Version 2010-03-24

Inh alt sverzeichni s

1 Motivation

2

2 Versionen

3

3 Datentypen

4

3.2.1

Wahrheitswerte

4

3.2.2

Zahlen

4

3.2.3

Zeichenketten

5

3.2.4

Umwandlung in einen anderen Datentyp

5

3.3.1

Objekte erstellen

6

3.3.2

Vordefinierte Eigenschaften und Methoden

7

3.3.3

Referenz auf Objekte

9

3.4.1

== und !=

9

3.4.2

=== und !==

10

3.4.3

> und <

10

3.4.4

<= und >=

11

4 Funktionsobjekte und Variablen

11

4.7.1

Globale Variablen

18

4.7.2

Das globale Objekt von HTML-Seiten

18

5 Klassen

19

5.1.1

Objekte initialisieren mit den Methoden „call“ oder „apply“

19

5.1.2

Der new-Operator

19

5.1.3

Die Objekt-Eigenschaft „prototype“

20

5.1.4

Die Eigenschaft „constructor“

21

5.3.1

Statische Eigenschaften

25

5.3.2

Statische Methoden

26

6 Vererbung

30

7 Ausnahmen fangen und werfen

35

8 Weiterführende Informationen

38

Seite 1

1

Motivation

Dieser Artikel beschäftigt sich mit der objektorientierten Programmierung in JavaScript (Version 1.5) und entstand aus der Motivation heraus komplexe Progamme für Web 2.0-Anwendungen zu erstellen. Mehrere Gründe sprechen für eine Verwendung der OOP in JavaScript:

JavaScript ist objektorientiert.

Bessere Wiederverwendbarkeit von Quellcode

Anwendung von Entwurfsmustern

Erweiterung der (Standard-) Klassen

Seite 2

2

Versionen

JavaScript wurde original von Netscape entwickelt und im Netscape -Browser 2.0 1995 integriert. Wegen des großen Erfolges von JavaScript in Webseiten entwickelte Microsoft aus markenrechtlichen Gründen den kompatiblen Dialekt JScript. Netscape beantragte bei Ecma International die Standardisierung der Sprache, welche dort unter der Bezeichnung ECMA-262 registriert ist. Die erste Veröffentlichung des Standards erfolgte 1997.

2.1 ECMAScript

ECMAScript ist der Name der nach ECMA-262 standardisierten Sprache. Der Name war ein Kompromiss zwischen den an der Standardisierung beteiligten Unternehmen Microsoft und Netscape . Die meisten aktuellen Implementierungen unterstützen den Standard ECMA-262, 3. Ausgabe (veröffentlicht 1999). In dieser Version wurden reguläre Ausdrücke, bessere String-Handhabung, neue Control-Statements, try/ catch-Ausnahmebehandlung, bessere Fehlerdefinition und Formatierung von numerischen Ausgaben hinzugefügt.

2.2 JavaScript

JavaScript ist ein Dialekt und Weiterentwicklung von ECMAScript und gleichzeitig eine geschützte Markenbezeichnung von Sun Microsystems . Netscape und später der Mozilla Foundation, welche den Namen unter einer Lizenz nutzt. Gegenwärtig wird JavaScript von der Mozilla Foudation weiterentwickelt. Die letzte Version ist 1.8, welche im Mozilla Firefox 3.0 und deren Nachfolger implementiert ist. JavaScript ist seit Version 1.5 kompatibel zur 3. Ausgabe des Standards ECMA-262.

2.3 JScript

JScript ist die Implementierung eines Dialektes von ECMAScript. Die Internet Explorer ab Version 5.5 unterstützen die 3. Ausgabe des Standards ECMA-262 und sind in etwa kompatibel zu JavaScript 1.5. Die Unterschiede zu JavaScript bestehen im wesentlichen in der unterschiedlichen implementierung des DOM -Models. Dieses ist jedoch nicht Bestandteil der Sprache ECMAScript sondern eine Standardisierung des W3C-Konsortiums.

2.4 Implementierungen

Folgende Anwendungen verwenden Imlementierungen von ECMAScript nach der 3. Ausgabe des Standards

ECMA-262:

Anwendung

 

Dialekt und aktuelle Version

 

Google Chrome

ECMAScript

Mozilla Firefox

JavaScript 1.8.1

 

Internet Explorer

JScript 5.8

Opera

ECMAScript

mit

einigen

Erweiterungen

von JavaScript

1.5

und

JScript.

Konqueror

JavaScript 1.5

Safari

JavaScript 1.5

Microsoft .NET

JScript .NET 8.0

 

Adobe Flash und Flex

ActionScript 3, mit einigen Implementierungen des Entwurfes der 4. Ausgabe von ECMAScript.

Adobe Acrobat

JavaScript 1.5

7

Seite 3

3

Datentypen

3.1 Übersicht

JavaScript kennt 5 verschiedene Datentypen:

Eigenschaft

Beschreibung

Beispiel

undefined

nicht definierter Wert

void 1

 

boolean

Wahrheitswert

true

number

Gleitkommazahlen

-12

string

Zeichenketten

'Hallo Welt!'

 

object

Zusammengesetzter Datentyp

{

a: 12 }

Mit dem typeof-Operator kann der Typ eines Wertes ermittelt werden:

console.log( '%s', typeof undefined ); // 'undefined'

console.log( '%s', typeof false );

// 'boolean'

console.log( '%s', typeof 1 );

// 'number'

console.log( '%s', typeof 'Hello' );

// 'string'

console.log( '%s', typeof {} );

// 'object'

console.log( '%s', typeof null );

// 'object'

null ist eine Referenz, die auf kein Objekt verweist. Das ist auch der Unterschied zwischen undefined und null.

3.2 Einfache Datentypen

3.2.1 Wahrheitswerte

Ein Wahrheitswert kann nur zwei Werte haben: wahr und falsch. Beide Zustände werden durch die Literale true und false ausgedrückt:

var wahr = true; var falsch = false;

Der Operator für die Negation ist das Ausrufezeichen:

var wahr = !false;

3.2.2 Zahlen

In JavaScript gibt es nur einen numerischen Datentyp: numeric. Zur Speicherung verwendet JavaScript 8- byte nach der Norm IEEE_754. Es gibt keinen speziellen Datentyp für Ganzzahlen, jedoch gibt es spezielle numerische Werte:

Literal

Beschreibung

NaN

Keine Zahl (Not a Number)

Infinity

positiv unendlich

-Infinity

negativ unendlich

-0

etwas kleiner als 0

+0

etwas größer als 0

Seite 4

var keineZahl = NaN; var pUnendlich = Infinity; var nUnendlich = -Infinity;

3.2.3 Zeichenketten

Zeichenketten werden in JavaScript entweder in einfachen oder doppelten Hochkommas begrenzt. Das Backslash-Zeichen leitet eine Escape-Sequenz ein.

var s1 = 'ab\ncd'; var s2 = "ab\ncd";

Zeichenketten werden durch den +-Operator verbunden.

'ab' + 'cd'; // 'abcd'

3.2.4 Umwandlung in einen anderen Datentyp

Durch Anwendung von Operatoren können einfache Datentypen in einen anderen Typ umgewandelt werden. Um einen Datentyp in einen Boolean-Typ umzuwandeln wird !! verwendet. Dabei gelten folgende Umwandlungsregeln:

Typ

 

Ergebniss

Beispiel

undefined

false

!! undefined

number

false, wenn 0, -0, +0 oder NaN; sonst true !! 12.3

string

false, wenn die Länge der Zeichenkette 0 ist; sonst true

!! 'a'

object

null liefert

false; ansonsten true

!! {}

Wenn man das negierte Ergebniss erhalten möchte, reicht eine einfache Negation aus, da die eigentliche Umwandlung bereits durch den ersten Negator erreicht wird. Operatoren, wie zum Beispiel if oder while, die einen Boolean-Typ erwarten, führen die Umwandlung in einen Boolean-Typ selbst aus. Um eine Zeichenkette in einen numerischen Wert umzuwandeln existieren in JavaScript 2 Funktionen:

parseInt und parseFloat:

Signatur

Beschreibung

Beispiel

parseInt(string, [basis])

Parst eine Zeichenkette als Ganzzahl. basis gibt die Basis des Zahlensystems an.

parseInt('12',10)

parseFloat(string)

Parst eine Zeichenkette als Gleitkommazahl.

parseFloat('-3.2E+3')

Beide Funktionen geben NaN zurück, wenn das Parsen fehlschlug. Führende Leerzeichen werden ignoriert. Nichtnumerische Zeichen brechen das Parsen der Zeichenkette ab und die bis dahin gültige Teilzeichenkette wird in einen numerischen Wert umgewandelt. Wird bei parseInt der zweite Parameter basis weggelassen, interpretiert parseInt die Zeichenkette als Zahl zur Basis 10. Beginnt die Zeichenkette jedoch mit einer führenden 0, wird als Basis 8 (oktal) angenommen. Der Prefix '0x' impliziert ein Parsen zur Basis 16 (hexadezimal). Beispiele:

Seite 5

console.log( '%s', parseInt('abc') );

// NaN

console.log( '%s', parseInt('') );

// NaN

console.log( '%s', parseInt(' \t\n\r -1') ); // -1

console.log( '%s', parseInt('06') );

// 6

console.log( '%s', parseInt('08') );

// 0

console.log( '%s', parseInt('08', 10) );

// 8

console.log( '%s', parseInt('0x12') );

// 18

console.log( '%s', parseInt('12', 16) );

// 18

console.log( '%s', parseInt('7z') );

// 7

Eine weitere Möglichkeit einen einfache Datentypen in eine Zahl umzuwandeln ist das Substrahieren von 0. Für Zeichenketten gilt dabei, dass leere Zeichenketten als 0, ansonsten als Dezimalzahlen interpretiert werden. Beginnt eine Zeichenkette mit '0x', wird sie als hexadezimale Zahl interpretiert. Nichtnumerische Zeichen liefern NaN. Wahrheitswerte geben 0 bei false und 1 bei true zurück, undefined ergibt NaN und null ergibt 0.

console.log( '%s', undefined - 0 );

// NaN

console.log( '%s', null - 0

);

// 0

console.log( '%s', 'abc' - 0

);

// NaN

console.log( '%s', '' - 0

);

// 0

console.log( '%s', ' \t\n\r -1' - 0 ); // -1

console.log( '%s', '06' - 0 );

// 6

console.log( '%s', '08' - 0 );

// 8

console.log( '%s', '0x12' - 0 );

// 18

console.log( '%s', '12' - 0 );

// 12

console.log( '%s', '7z' - 0 );

// NaN

console.log( '%s', false - 0

);

// 0

console.log( '%s', true - 0

);

// 1

/* Achtung ! */ console.log( '%s', '7' + 0 );

// '70'

Um eine Zeichenkette aus einem anderen Datentyp zu erzeugen, wird die leere Zeichenkette '' mit dem +- Operator hinzugefügt. Beispiele:

console.log( '%s', '' + 0 ); console.log( '%s', '' + false ); console.log( '%s', '' + true ); console.log( '%s', '' + -12.3 ); console.log( '%s', '' + NaN ); console.log( '%s', '' + Infinity ); console.log( '%s', '' + -Infinity ); console.log( '%s', '' + null ); console.log( '%s', '' + undefined );

// '0' // 'false' // 'true' // '-12.3' // 'NaN' // 'Infinite' // '-Infinite' // 'null' // 'undefined'

3.3

Objekte

3.3.1 Objekte erstellen

Ein Objekt ist eigentlich nichts anderes als ein „Ding" mit Eigenschaften. Diese Eigenschaften wiederum können Objekte sein. In PHP würde man solch ein Objekt als Instanz der Klasse stdClass definieren:

Seite 6

<?php

$person

= new stdClass;

$person->id = 12; $person->name = 'Walter'; $person->kinder = null;

In JavaScript kann ein leeres Objekt wie folgt definiert werden:

var person = new Object();

oder auch in der Literalschreibweise:

var person = {};

Diesem Objekt kann man nun die Eigenschaften zuweisen.

person.id = 12; person.name = 'Walter'; person.kinder = null;

Gewissermaßen verhält sich ein Objekt wie ein assoziatives Feld in PHP. Der Schlüssel ist der Name der Eigenschaft. Dies verdeutlicht auch der []-Operator. Das gleiche Beispiel wie oben in einer anderen Syntax:

var person = new Object(); person['id'] = 12; person['name'] = 'Walter'; person['kinder'] = null;

Eine weitere Möglichkeit zur Definition von Objekten ist die Verwendung der Literalschreibweise:

var person = {

id

: 12,

name

: 'Walter',

kinder : null

};

Methoden sind gewöhnliche Eigenschaften von Objekten und können zum Beispiel wie folgt angelegt werden:

var person = { id

: 12,

name

: 'Walter',

toString

: function(){

return 'Walter';

}

}; console.log( '%s', person.toString() );

3.3.2 Vordefinierte Eigenschaften und Methoden

Objekte haben vordefinierte Eigenschaften und Methoden :

Eigenschaft

Beschreibung

constructor

Funktion zur Erzeugung von Objekt-Prototypen

prototype

Der Prototyp des Objektes. Erlaubt das Hinzufügen von Eigenschaften zu allen Objekten des gleichen Typs.

Seite 7

Methode

Argumente

 

Beschreibung

hasOwnProperty

prop

Gibt wahr zurück, wenn das Objekt die eigene Eigenschaft prop hat

isPrototypeOf

obj

Prüft, ob das Objekt ein Prototyp des angegebenen Objektes ist

propertyIsEnumerable

Diese Methode prüft, ob die Eigenschaft mit einer

for

in – Schleife durchlaufen werden kann

toString

Gibt das Objekt als Zeichenkette zurück

valueOf

Wandelt das Objekt in einen einfachen Datentyp um

var person = new Object(); person['id'] = 12; person['name'] = 'Walter'; person['kinder'] = null; console.log( '%s', person.propertyIsEnumerable('name') );

valueOf und toString sind zwei wichtige Methoden, um ein Objekt in einen einfachen Datentyp umzuwandeln. Das Standard-Verhalten der valueOf-Methode ist, dass sie das Objekt selbst zurück gibt, toString gibt eine nicht-leere Zeichenkette zurück. Ist ein anderes Verhalten gewünscht, müssen beide Methoden überschrieben werden:

var obj = { id : 13, valueOf : function(){ return this.id;

}, toString : function(){ return 'Objekt ' + this.id;

};

}

console.log( '%s', '' + obj ); console.log( '%s', obj.toString() ); console.log( '%s', obj - 0 ); console.log( '%s', !obj );

// '13' // 'Objekt 13' // 13 // false

Das Beispiel zeigt, das zum Erzeugen einer Zeichenkette aus einem Objekt die Methode valueOf zuerst aufgerufen wird. Gibt valueOf einen einfachen Datentyp zurück, dann wird dieser in eine Zeichenkette umgewandelt. Anders ist das Verhalten, wenn valueOf keinen einfachen Datentyp zurück gibt. Das ist das Standardverhalten von Objekten; valueOf gibt das Objekt selbst zurück. Dann wird toString aufgerufen, um einen einfachen Datentypen zu erzeugen:

Seite 8

var obj = { id : 13, /* JavaScript Standard für Objekte:

valueOf : function(){ return this;

}, */ toString : function(){ return '' + this.id;

};

}

console.log( '%s', '' + obj );

// '23'

console.log( '%s', obj - 0 );

// 23

3.3.3 Referenz auf Objekte

JavaScript vewendet Referenzen auf Objekte ähnlich wie in Java. Folgendes Beispiel verdeutlicht das:

var person = new Object(); var vater = person;

vater.name = 'Walter'; console.log( '%s', person.name); /* 'Walter' */

Da vater nur eine Referenz auf eine Person ist, erhält auch person den Namen „Walter“. Möchte man auf nichts referenzieren, verwendet man das Schlüsselwort null:

var thronfolger = null;

3.4

Vergleichsoperatoren

3.4.1 == und !=

Der Operator == vergleicht zwei Werte auf Gleichheit und gibt entweder true oder false zurück. Es gilt immer: ist a == b, dann gilt auch b == a. Sind beide Werte vom gleichem Datentyp, so ist nur zu beachten, dass ein Vergleich eines numerischen Wertes mit NaN immer false zurück gibt und der Vergleicxh zweier Objekte true liefert, wenn beide auf das gleich Objekt referenzieren:

var o1 = {}; var o2 = o1; console.log( '%s', 12 == NaN );

// false

console.log( '%s', NaN == NaN );

// false

console.log( '%s', {} == {} );

// false

console.log( '%s', o1 == o2 );

// true

Weitaus komplizierter ist der Vergleich, wenn die beiden Werte unterschiedliche Datentypen haben. Dabei gelten folgende Regeln:

Ein Vergleich mit NaN liefert imer false

Ein Vergleich mit null ergibt immer false, außer beim Vergleich mit undefined

Ein Vergleich mit undefined ergibt immer false außer beim Vergleich mit null Um zwei Werte miteinander vergleichen zu können, finden folgende impliziten Umwandlungen statt:

Objekte werden zum Vergleich in einen einfachen Datentypen umgewandelt, wenn das Objekt nicht schon null ist.

Seite 9

Boolean-Werte werden in einen numerischen Wert umgewandelt (0 oder 1)

Zeichenketten werden in einen numerischen Wert umgewandelt Ein Vergleich zweier Werte mit unterschiedlichem Datentyp sollte immer vermieden werden, da die Vergleichsergebnisse durchaus nicht intuitiv sind. Beispiele:

console.log( '%s', null == NaN );

// false

console.log( '%s', null == undefined );

// true

console.log( '%s', {} == false );

// false

console.log( '%s', [] == ![] );

// true

console.log( '%s', [] == [] );

// false

console.log( '%s', 2 == true );

// false

console.log( '%s', '03' == 3 );

// true

console.log( '%s', '' == false );

// true

console.log( '%s', null == false );

// false

Ein typsicheren Vergleich zwischen zwei Werten a und b wird wie folgt ermöglicht:

Vergleich als

 

Umsetzung

boolean

 

!a == !b

number

a

-

0 == b

-

0

string

''

+

a == '' + b

Für den Vergleichsoperator != gilt: wenn !(a == b), dann ist a != b und umgekehrt.

3.4.2 === und !==

Der Operator === verhält sich wie ==, nur dass er immer false zurück gibt, wenn die Vergleichswerte unterschiedliche Datentypen haben. Außerdem gilt: wenn !(a === b), dann ist a !== b und umgekehrt. Die beiden Operatoren === und !== sollten in der Verwendung immer den Vorzug gegenüber == und != haben, da sie sich intuitiver verhalten.

3.4.3 > und <

Die Operatoren < und > dienen zum Vergleich numerischer Werte und von Zeichenketten. Für numerische Werte ergibt ein Vergleich mit NaN und undefined immer false.

console.log( '%s', 13 > NaN );

// false

console.log( '%s', 13 < NaN );

// false

console.log( '%s', 13 > undefined );

// false

console.log( '%s', 13 < undefined );

// false

console.log( '%s', Infinity > Infinity ); // false

Bei anderen Datentypen versucht JavaScript die Werte in Zahlen umzuwandeln. Gelingt das für beide Werte nicht, dann erfolgt der Vergleich zeichenkettenbasiert zeichenweise. Es ist nicht möglich den Vergleich an lokale Umgebungen anzupassen.

Seite 10

console.log( '%s', '4' > '3' );

// true

console.log( '%s', '4' < '3' );

// false

console.log( '%s', '4' > '03' );

// true

console.log( '%s', '4' < '03' );

// false

console.log( '%s', '4' > '0x03' ); // true console.log( '%s', '4' < '0x03' ); // false

console.log( '%s', false > '' );

// false

console.log( '%s', false < '' );

// false

console.log( '%s', true > '' );

// true

console.log( '%s', true < '' );

// false

console.log( '%s', false > 'x' );

// false

console.log( '%s', false < 'x' );

// false

console.log( '%s', 13 > null );

// true

console.log( '%s', 13 < null );

// false

console.log( '%s', 13 > '03' );

// true

console.log( '%s', 13 < '03' );

// false

Wie beim ==-Operator gilt: ein Vergleich mit unterschiedlichen Datentypen sollte vermieden werden.

3.4.4 <= und >=

Die Operatoren <= und >= verhalten sich analog zu < und >: Sie geben false zurück, wenn ein Vergleich mit undefined oder NaN erfolgt. Andernfalls gilt: a <= b entspricht !(a > b) und a >= b entspricht !(a < b).

4 Funktionsobjekte und Variablen

4.1 Funktionsobjekte erstellen

In JavaScript gibt es keine Funktionen sondern Funktionsobjekte, die wie ein normales Objekt Eigenschaften haben können. Im folgenden Beispiel wird ein Funktionsobjekt definiert, das die übergebene Zeichenkette ausgibt:

var func = function(zeichenkette) { console.log( '%s', zeichenkette);

}

func('Hello world!');

Wie man sieht kann man ein Funktionsobjekt wie eine normale Funktion aufrufen. Selbstverständlich kann man ein Funktionsobjekt auch ganz klassisch definieren. Die folgenden Anweisungen sind mit denen im oberen Beispiel identisch:

function func(zeichenkette) { console.log( '%s', zeichenkette);

}

func('Hello world!');

oder auch:

var func = new Function('zeichenkette', 'console.log(zeichenkette)'); func('Hello world!');

Die letzte Schreibweise kann verwendet werden, um Funktionen dynamisch mit JSON zu erstellen. Die Begriffe Funktion und Funktionsobjekt sind in JavaScript vollkommen gleichbedeutend. Da eine Funktion ein ganz normales Objekt ist, kann man einer Funktion auch Eigenschaften geben:

Seite 11

var func = function() { console.log( '%s', func.zeichenkette);

}

func.zeichenkette = 'Hello world!';

func(); /* 'Hello world!' */

In diesem Beispiel gibt die Funktion func() ihre Eigenschaft zeichenkette aus. Ein typische Anwendungsfall ist die Verwendung als Callback:

var callback = function(zahl) { console.log( '%s', callback.zeichenkette + ' ' + zahl);

}

callback.zeichenkette = 'Ausgabe von';

var run = function(callback) {

callback(12);

}

run(callback); /* 'Ausgabe von 12' */

4.2 Eigenschaften und Methoden

Alle Funktionsobjekte haben vordefinierte Eigenschaften und Methoden:

Eigenschaft

Beschreibung

length

Anzahl der erwarteten Argumente der Funktion

Methode

 

Argumente

Beschreibung

apply

this [,argArr]

Erlaubt die Anwendung der Funktion auf ein Objekt this. Die Funktionsargumente werden als Feld übergeben.

call

this

Wie apply, jedoch werden die Argumente wie bei einem normalen Funktionsaufruf übergeben.

[,arg1 [,arg2

[,

]]]

 

Da Funktionsobjekte auch Objekte sind, haben Funktionsobjekte auch Eigenschaften und Methoden von Objekten (siehe 3.3.2).

4.3 Variablen

Wird eine Funktion ausgeführt, legt JavaScript ein Aktivierungsobjekt an. Die Eigenschaften des Aktivierungsobjektes heißen Variablen. Auf Variablen wird ohne Angabe des Objektes zugegriffen. Das Aktivierungsobjekt besitzt eine vordefinierte Eigenschaft arguments, die vom Typ Array ist und alle Argumente der Funktion enthält. Die Argumente der Funktion sind selbst Eigenschaften des Aktivierungsobjektes (also Variablen):

Seite 12

var func = function(a, b, c) { console.log( '%s', arguments.length); for ( var i = 0; i < arguments.length; ++i) { console.log( '%s', i + '. Argument : ' + arguments[i]);

}

console.log( '%s', a + ' ' + b + ' ' + c);

}

func('x', 'y', 'z');

Die Variable arguments hat außerdem eine Eigenschaft callee, welches die aufgerufene Funktion selbst ist und dazu verwendet werden kann um die Funktion rekursiv aufzurufen:

var fakultaet = function(n) {

if (

n

> 1 ){

return n * arguments.callee(n-1); } else { return 1;

}

}

fakultaet(5);

Um dem Aktivierungsobjekt neue Eigenschaften hinzuzufügen, verwendet man das Schlüsselwort var:

var func = function() { var x = 13; var f = function(a) { console.log( '%s', a);

}

f(x);

}

func(); /* Ausgabe: 13 */

JavaScript deklariert alle Variablen zu Beginn der Funktion, egal an welcher Stelle das wirklich erfolgte:

var x = 1; var func = function() { console.log( '%s', x );/* Ausgabe: undefined */ var x = 2;

}

func();

4.4

Namensräume

Auch JavaScript unterstützt Namensräume. Wie man schon erwartet ist ein Namensraum auch ein Objekt. Empfehlenswert ist für jede API einen eigenen Namensraum zu definieren. Wie bei Java üblich, könnten sich Objekte beispielsweise im Namensraum de.unister befinden. Bsp.:

Seite 13

// Definition Namensraum var de = { unister : {}

}

de.unister.obj = { val : 12 }; de.unister.func = function(){ console.log( '%s', de.unister.obj.val);

}

de.unister.func(); // Ausgabe: 12

Bezeichner für Namensräume können sehr lang werden. Daher ist es empfehlenswert, einen Alias bei der Verwendung anzulegen:

var du = de.unister; du.obj = { val : 12 }; du.func = function(){ console.log( '%s', du.obj.val);

}

du.func(); // Ausgabe: 12

Mit dem Schlüsselwort with kann man einen Namensraum betreten:

var du = du.obj = du.func

de.unister; { val : 12 }; =

console.log( '%s', du.obj.val);

function(){

}

// Namensraum betreten with ( de.unister ) { func(); // Ausgabe: 12

}

Beim Betreten des Namensraumes ist zu beachten, dass mit with zuerst nach Eigenschaften im Namensraum gesucht wird und erst dann im Aktivierungsobjekt:

var val = 1; de.unister.val = 2;

// Namensraum betreten with ( de.unister ) { console.log( '%s', val); // Ausgabe: 2

}

4.5 Die Variable „this“

Die Variable this es ist:

ist eine Referenz auf das Objekt, zu dem das Funktionsobjekt gehört, also dessen Methode

Seite 14

var obj = { 'name' : 'Walter', 'func' : function() { console.log( '%s', this.name);

}

}

obj.func(); /* 'Walter' */

In diesem Beispiel gehört die Funktion func dem Objekt obj. Somit ist func ist eine Methode von obj. Ist eine Funktion in einem Aktivierungsobjekt definiert wurden, dann referenziert this auf das globale Objekt self (siehe Kapitel 4 Das globale Objekt Seite 17)

var func = function() { console.log('%s', this === self);

}

func(); /* true */

Die Variable this kann beim Aufruf der Funktion durch die Methoden des Funktionsobjektes call und apply gesetzt werden. Dadurch erhält die Funktion einen neuen Kontext. Beispiel:

var obj = { 'name' : 'Walter', 'func' : function() { console.log('%s', this.name);

}

}

var test = { 'name' : 'Horst'

}; obj.func.call(test); /* 'Horst' */

Insgesamt gibt 3 Möglichkeiten den Kontext zu setzen:

Typ

Aufruf durch

this

Methodenaufruf

obj.methode(arg1,arg2)

obj

call-Methode der

func.call(obj,arg1,arg2)

obj

Funktion

apply-Methode der Funktion

func.apply(obj,[arg1,arg2])

obj

4.6 Die Gültigkeitsbereichskette

Variablen sind nicht nur innerhalb der Funktion sichtbar, die gerade aufgerufen wird. Wird ein Funktionsobjekt erzeugt, so wird es mit dem Aktivierungsobjekt der erzeugenden Funktion verbunden. Dadurch entsteht eine Gültigkeitsbereichskette (engl.: scope chain ) von Aktivierungsobjekten und somit von Variablen. Wird eine Funktion ausgeführt, wird das dabei erzeugte Aktivierungsobjekt an den Anfang dieser Kette gestellt. Am Ende der Kette befindet sich immer das globale Objekt (siehe 4). JavaScript nutzt nun diese Gültigkeitsbereichskette, um nach Variablen zu suchen:

1. Hole das nächste Objekt in der Gültigkeitsbereichskette. Gibt es keins, wird null zurückgegeben.

2. Rufe vom Ergebnis aus 1. die Methode hasProperty auf, der als Argument der Variablenname übergeben wird.

3. Wenn das Ergebnis von 2. „wahr“ ist, gib eine Referenz auf die gefundene Eigenschaft des

Seite 15

Aktivierungsobjektes aus der Gültigkeitsbereichskette zurück.

4. Gehe zu 1.

Zusammengefasst bedeutet das: Der Gültigkeitsbereich einer Variable ist definiert durch die Stelle im Quellcode, wo die dazugehörige Funktion definiert wurde. Eingebette Funtionsobjekte haben Zugriff auf Variablen im umgebenden äußeren Gültigkeitsbereich. Der äußerste Gültigkeitsbereich ist das globale Objekt (siehe Kapitel 4 Das globale Objekt Seite 17). Darin besteht auch eine Fehlerquelle: Werden Variablen nicht mit var deklariert, wird automatisch in einem anderen Aktivierungsobjekt der Gültigkeitsbereichskette oder im globalen Objekt diese Eigenschaft gesucht. Umgekehrt ist jede Variable sichtbar in Funktionen, die im gleichen Aktivierungsobjekt definiert wurden. Daher sollte jede Variable mit var deklariert werden, es sei denn, man beabsichtigt die globale Sichtbarkeit. Beispiel:

var a = 12; var f1 = function() { var a = 2;

f2();

}

var f2 = function() {

var a = 8;

f3();

}

var f3 = function() { console.log('%s', a);

}

f1(); /* 12 */

In diesem Beispiel wird beim Aufruf von f3() die Eigenschaft a im aktuellen Aktivierungsobjekt gesucht, aber nicht gefunden. Das nächste Aktivierungsobjekt in der Gültigkeitskette ist das Aktivierungsobjekt, in dem die Funktion f3() definiert wurde. Dort wird a mit dem Wert 12 gefunden. Daher gibt das Beispiel 12 aus. Jetzt verändern wir die Gültigkeitsbereichskette, indem wir die Definition der Funktionen in anderen Aktivierungsobjekten durchführen:

var a = 12; var f1 = function() { var a = 2; var f2 = function() { var f3 = function() { console.log('%s', a);

}

f3();

}

f2();

}

f1(); /* 2 */

Die Funktion f1() ruft die Funktion f2() auf, diese wiederum f3(). Beim Aufruf von f3() wird im aktuellen Aktivierungsobjekt keine Eigenschaft a gefunden. Das nächste Aktivierungsobjekt in der Gültigkeitsbereichskette ist das Aktivierungsobjekt von f2(), da dort f3() definiert wurde. Auch dieses Objekt hat keine Eigenschaft a. Daher wird im Aktivierungsobjekt von f1() nach der Eigenschaft a gesucht, welche dann auch gefunden wird. Daher wird 2 ausgegeben. Aktivierungsobjekte werden von JavaScript nach der Ausführung der Funktion nicht verworfen, wie das folgende Beispiel demonstriert:

Seite 16

var f1 = function() { var a = 13; var func = function() { console.log('%s', a);

}

}

a

return func;

= 7;

var f2 = f1(); f2(); /* 7 */

Der Aufruf der Funktion f1() erzeugt eine weitere Funktion f2(). Diese gibt 7 aus, da f2() in der Funktion f1() definiert wurde und a nach der Ausführung von f1() den Wert 7 hat. Das folgende Beispiel dient zur Übung und ist deutlich komplizierter, funktioniert aber nach den oben beschriebenen Regeln:

var a = 1; var obj = { a : 2, func : function() {
var a = 1;
var obj = {
a : 2,
func : function() {
var a = 3;
this.a = 4;
this.test();
a = 5;
}
}
var f1 = function() {
var a = 6;
var f2 = function() {
a = 7;
obj.test = function() {
console.log('%s', a);
}
a
= 8;
}
f2();
a = 9;
}
a = 10;
f1();
obj.func(); /* 9 */

Hier wird durch den Aufruf von obj.test() 9 ausgegeben. Die Ursache hierfür ist, dass die Eigenschaft a des Aktivierungsobjektes nach der Ausführung von f1() den Wert 9 hat. Erst danach wird ob.func() und somit obj.test() ausgeführt.

4.7 Das globale Objekt

In JavaScript gibt es ein genau ein Objekt, das sich am Ende aller Gültigkeitsbereichsketten befindet.

Seite 17

4.7.1

Globale Variablen

Da das globale Objekt am Ende der Gültigkeitsbereichskette steht, verhalten sich Eigenschaften des globalen Objektes wie globale Variablen:

<script>

x = 12;

var func = function() { console.log('%s', x);

}

func(); /* 12 */

</script>

Findet JavaScript beim Abarbeiten der Gültigkeitsbereichskette ein Objekt, in dem eine Eigenschaft mit dem gleichen Bezeichner definiert ist, dann wird wie üblich diese Variable verwendet:

<script>

x = 12;

var func = function() {

var x = 3; console.log('%s', x);

}

func();

</script>

4.7.2 Das globale Objekt von HTML-Seiten

Beim Erzeugen des globalen Objektes wird dem globalen Objekt die Eigenschaft self hinzugefügt. Diese Eigenschaft referenziert auf das globale Objekt, so dass im folgenden Beispiel true zurückgegeben wird:

console.log('%s', this === self)

Dadurch ist self eine globale Variable geworden und somit das globale Objekt selbst, was dann auch den Namen des Objektes erklärt.

<html>

<head> <script language= “javascript“ > this.x = 13; console.log( '%s', self.x); console.log( '%s', this.self.self.self.x); </script> </head> <body>

:

Zusätzlich gibt es noch eine weitere Eigenschaft des globalen Objektes, die Eigenschaft window. Wird eine Seite in einem Frameset dargestellt, dann ist window die Eigenschaft des globalen Objektes, das das Framset erzeugt, anderenfals ist window das globale Objekt selbst. Somit kann man prüfen, ob eine Seite in einem Frame dargestellt wird:

if (self === window) { console.log( '%s', 'Ich bin in keinem Frame.'); } else { console.log( '%s', 'Ich bin in einem Frame!');

}

Seite 18

4.8

Eval

Code, der mit eval ausgewertet wird, erhält den Kontext des Aufrufers: dessen Gültigkeitsbereichskette, dessen Aktivierungsobjekt und dessen this-Wert.

var x = 1; var f2 = null; var f1 = function(){ var y = 2; eval('y=12; x=13; f2 = function(){ console.log("%s",y);}'); y = 3;

}

f1();

f2();

console.log('%s', x);

5

Klassen

5.1 Konstruktoren

Der Konstruktor wird bei der Erzeugung der Objekte aufgerufen. Er wird dazu verwendet, die Objekte in einen definierten Anfangszustand zu versetzen.

5.1.1 Objekte initialisieren mit den Methoden „call“ oder „apply“

Mit den Methoden call() und apply() von Funktionsobjekten kann der Kontext this des Aktivierungsobjektes geändert werden. Wenn die Funktion auf ein leeres Objekt angewendet wird, dann kann diese Funktion Eigenschaften des Objektes definieren und verhält sich somit wie ein Konstruktor. Das folgende Beispiel erzeugt zwei Brüche mit der Konstruktorfunktion Bruch:

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

this.toString = function() { return this.zaehler + '/' + this.nenner;

}

}

var dreiviertel = new Object(); var einhalb = new Object();

Bruch.call(dreiviertel, 3, 4); Bruch.call(einhalb, 1, 2);

console.log( '%s', dreiviertel); /* '3/4' */ console.log( '%s', einhalb); /* '1/2' */

5.1.2 Der new-Operator

Dem Operator new kann jedes Funktionsobjekt als Konstruktor übergeben werden. Der Operator new erstellt ein neues Objekt ohne Eigenschaften und wendet auf dieses Objekt den angegebenen Konstruktor an. Jede JavaScript -Funktion kann als Konstruktor verwendet werden.

Seite 19

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

this.toString = function() { return this.zaehler + '/' + this.nenner;

}

}

var dreiviertel = new Bruch(3, 4); var einhalb = new Bruch(1, 2);

console.log( '%s', dreiviertel); /* '3/4' */ console.log( '%s', einhalb); /* '1/2' */

5.1.3 Die Objekt-Eigenschaft „prototype“

Die Objekteigenschaft prototype hat nichts mit der gleichnamigen JavaScript -Bibliothek zu tun. Wenn in JavaScript ein Objekt mit dem new-Operator erzeugt wird, dann referenziert dieses Objekt implizit auf die Eigenschaft prototype des Konstruktors. Dadurch erhält das erzeugte Objekt weitere Eigenschaften, die jedoch nicht selbst Eigenschaften des erzeugten Objektes sind, sondern Eigenschaften dieses Prototypen. Mit Hilfe der Eigenschaft prototype des Konstruktors lassen sich somit Eigenschaften und Methoden zu Objekten hinzufügen:

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

}

var dreiviertel = new Bruch(3, 4);

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

console.log( '%s', dreiviertel); /* 'Bruch 3/4' */ console.log( '%s', dreiviertel.hasOwnProperty('zaehler')); /* true */ console.log( '%s', dreiviertel.hasOwnProperty('toString')); /* false */ console.log( '%s', !dreiviertel.toString); /* false */

Im oberen Beispiel geschieht folgendes, um das Objekt dreiviertel mit console.log() auszugeben:

1. console.log sucht in dreiviertel die Methode toString() welche nicht gefunden wird

2. Die Methode toString() wird im Prototyp des Konstruktors (Bruch.prototype) gefunden.

3. Bruch.prototype.toString.call(dreiviertel) wird implizit ausgeführt.

Man kann sagen, dass ein mit new erstelltes Objekt die Eigenschaften des Prototyps des Konstruktors erhält. Alle Objekte referenzieren auf die Eigenschaft prototype ihres Konstruktors. Wird eine Eigenschaft des Prototypen geändert, so erhalten auch bereits instanziierte Objekte diese Eigenschaft, auch wenn sie schon vorher erzeugt wurden. Das gilt nicht, wenn diese Eigenschaften bei einem Objekt bereits überschrieben und somit zu eigenen Eigenschaften wurden. Prinzipiell sollten alle Methoden, die zum Überschreiben dienen, im Prototyp angelegt werden. Das ermöglicht dem Nutzer des Konstruktors eine bequeme Anpassung der zur Verfügung gestellten Funktionalität.

Seite 20

Andererseits kann dadurch resourcenschonender programmiert werden. Anstelle Eigenschaften und Methoden bei jedem Konstruktoraufruf zu definieren, geschieht das nur einmal im Prototypen des Konstruktors.

5.1.4 Die Eigenschaft „constructor“

JavaScript legt automatisch beim Erzeugen von Funktionen die Eigenschaft prototype und in diesem Objekt wiederum die Eigenschaft constructor an, welche auf die gerade erstellte Funktion referenziert:

var Test = function() {

}

console.log( '%s', Test === Test.prototype.constructor);

Damit haben alle mit Test instanziierten Objekte die (nicht eigene) Eigenschaft constructor, da diese Objekte mit dem Operator new mit dem Prototyp des Konstruktors verknüpft werden:

var Test = function() {

}

Test.toString = function() { return 'Test-Konstruktor';

}

var t = new Test(); console.log( '%s', '%s',t.constructor); /* 'Test-Konstruktor' */ console.log( '%s', t.hasOwnProperty('constructor')); /* false */

5.2 Öffentliche und private Objekteigenschaften

Aus anderen Programmiersprachen kennt man öffentliche (public), geschützte (protected) und private (private) Eigenschaften und Methoden von Objekten. In JavaScript sind alle Eigenschaften von Objekten öffentlich:

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(); console.log( '%s', bruch); /* 'Bruch 0/1' */

bruch.zaehler = 3; bruch.nenner = 4;

console.log( '%s', bruch); /* 'Bruch 3/4' */

Mit Hilfe der Gültigkeitsbereichskette lassen sich dennoch private Eigenschaften von Objekten simulieren. Hierzu wird das Aktivierungsobjekt des Konstruktoraufrufes verwendet, welches eigentlich nur während des Aufrufes innerhalb des Konstruktors sichtbar ist. Funktionsobjekte, die beim Konstruktoraufruf erzeugt werden, haben durch die Gültigkeitsbereichskette Zugriff auf das Aktivierungsobjekt. Somit sind verwendete Variablen im Konstruktor private Eigenschaften:

Seite 21

var Bruch = function(zaehler, nenner) { var _zaehler = zaehler || 0; var _nenner = nenner || 1;

this.toString = function() { return 'Bruch ' + _zaehler + '/' + _nenner;

}

}

var dreiviertel = new Bruch(3, 4); var einhalb = new Bruch(1, 2);

console.log( '%s', dreiviertel); /* 'Bruch 3/4' */ console.log( '%s', einhalb); /* 'Bruch 1/2' */

dreiviertel.nenner = 0; console.log( '%s', dreiviertel); /* 'Bruch 3/4' */

Da Methoden, die im Konstruktor definiert werden, Zugriff auf die Eigenschaften des Aktivierungsobjektes vom Konstruktoraufruf haben, nennt man diese Methoden priviligierte Methoden . Priviligierte Methoden haben den Nachteil, dass sie bei jeder Instanziierung erzeugt werden müssen. Das kann zu einem Performance-Verlust führen. Das folgende Beispiel legt zwar eine öffentliche Methode toString() an, jedoch ist diese Methode nicht priviligiert. Durch diese fehlerhafte Definition der Funktion kommt es hier sogar zu einer falschen Ausgabe.

var _zaehler = 2; var _nenner = 3

var Bruch = function(zaehler, nenner) { var _zaehler = zaehler || 0; var _nenner = nenner || 1;

}

var dreiviertel = new Bruch(3, 4); dreiviertel.toString = function() { return 'Bruch ' + _zaehler + '/' + _nenner;

}

console.log( '%s', dreiviertel); /* 'Bruch 2/3' */

Ein Problem beim Verwenden von privaten Methoden ist die Sichtbarkeit des this-Objektes, da bei Methoden des Aktivierungsobjektes das this-Objekt immer auf das globale Objekt referenziert. Hier kann man sich wie folgt behelfen:

Da privilegierte Methoden Sichtbarkeit auf die Eigenschaften des Aktivierungsobjektes haben in dem sie definiert wurden, legt man eine Variable an, die auf this referenziert. Üblicherweise hat diese Eigenschaft den Bezeichner me:

Seite 22

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

// Größter gemeinsamer Teiler var _ggt = function(m, n) { if (m < n) return _ggt(n, m); if (n == 0) return m; return _ggt(n, m % n);

}

// me ist Referenz auf this var me = this; var _kuerzen = function() { var t = _ggt(me.zaehler, me.nenner); me.zaehler /= t; me.nenner /= t;

}

_kuerzen();

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(6, 8); console.log( '%s', bruch); /* 'Bruch 3/4' */

Weitaus eleganter ist der Aufruf der Funktion _kuerzen() mit den Methoden call() oder apply() von Funktionsobjekten:

Seite 23

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

// Größter gemeinsamer Teiler var _ggt = function(m, n) { if (m < n) return _ggt(n, m); if (n == 0) return m; return _ggt(n, m % n);

}

var _kuerzen = function() { var t = _ggt(this.zaehler, this.nenner); this.zaehler /= t; this.nenner /= t;

}

_kuerzen.call(this);

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(6, 8); console.log( '%s', bruch); /* 'Bruch 3/4' */

Noch besser ist es, die Funktionen aus dem Konstruktor zu entfernen. Damit wird auf eine Erzeugung der Funktionen bei jedem Konstruktoraufruf verzichtet:

Seite 24

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1; this kuerzen();

}

/**

*

Größter gemeinsamer Teiler

*

@private

*/

Bruch.prototype

ggt

= function(m, n) {

ggt(n,

if (m < n) return this if (n == 0) return m;

return this

m);

ggt(n,

m % n);

}

/**

* Bruch kürzen

* @private

*/ Bruch.prototype var t = this

kuerzen

ggt(this.zaehler,

= function() {

this.nenner);

this.zaehler /= t; this.nenner /= t;

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(6, 8); console.log( '%s', bruch); /* 'Bruch 3/4' */

5.3 Klasseneigenschaften und Methoden

5.3.1 Statische Eigenschaften

Mit der constructor-Eigenschaft ist es möglich statische Eigenschaften zu definieren. Statische Eigenschaften werden aber nicht direkt im Konstruktor definiert, sondern in dessen Prototypen. Das ermöglicht erbenden Klassen den Zugriff auf diese Variable. (Siehe Seite 30 Kapitel 6 Vererbung). Das folgende Beispiel zählt die Konstruktoraufrufe und somit die Anzahl der erzeugten Brüche:

Seite 25

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1; this.constructor.prototype.anzahl++;

}

Bruch.prototype.anzahl = 0;

var dreiviertel = new Bruch(3, 4); var einhalb = new Bruch(1, 2); console.log( '%s', 'Wir haben ' + Bruch.prototype.anzahl + ' Brüche ');

Mit Hilfe der Gültigkeitsbereichskette ist es prinzipiell möglich, private statische Eigenschaften zu verwenden. Das folgende Beispiel implementiert ein Singleton-Muster:

var MySingletonClass = ( function() { var _instance = undefined; // private statische Eigenschaft var constructor = function() { if ( _instance !== true ) { throw new Error('Klasse ist ein Singleton.');

}

// Konstruktor-Code

}; constructor.getInstance = function() { if (!_instance) { _instance = true; // Setze "gültiger Konstruktoraufruf" _instance = new constructor();

}

return _instance;

}

return constructor; })();

console.log( '%s', MySingletonClass.getInstance() instanceof MySingletonClass ); /* true */ console.log( '%s', MySingletonClass.getInstance() === MySingletonClass.getInstance() ); /* true */

Das Objekt MySingletonClass wird durch das Ausführen einer anonymen Funktion erzeugt. Die Variable

instance sind nach außen nicht sichtbar und somit einzigste Instanz der Klasse MySingletonClass.

_instance ist die in diesem Beispiel die

privat.

5.3.2 Statische Methoden

Auch öffentliche statische Methoden werden im Prototypen wie nicht-statische Methoden definiert. Der Vorteil ist, dass statische Methoden somit auch nicht-statisch aufgerufen werden können. Ein weiterer Vorteil ist, dass erbende Klassen dann auch diese Methode kennen (Siehe Seite 30 Kapitel 6 Vererbung) und diese Methode gegebenenfalls überschreiben können. Im folgenden Beispiel wird die Methode Bruch.prototype.ggt() als statische Methode deklariert. Innerhalb dieser Methode wird diese wieder mit this.constructor.prototype.ggt(n,m) rekursiv

Seite 26

aufgerufen. Dieser zunächst umständlich wirkende Aufruf bewirkt, dass diese Methode auch nicht-statisch aufgerufen werden kann, da in diesem Kontext this.constructor gleich Bruch ist. Wird sie statisch aufgerufen, dann ist this gleich Bruch.prototype, da die Methode ggt() diesem Objekt gehört. Beim Erzeugen des Klassenkonstruktors Bruch setzt JavaScript Bruch.prototype.constructor = Bruch. Daher ist this.constructor gleich Bruch und somit this.constructor.prototype.ggt() gleich Bruch.prototype.ggt():

var Bruch = function(zaehler, nenner) { this.zaehler = zaehler || 0; this.nenner = nenner || 1; this kuerzen();

}

/**

* Größter gemeinsamer Teiler

* @static

*/ Bruch.prototype.ggt = function(m, n) { // Korrekter Aufruf einer statischen Methode if (m < n) return this.constructor.prototype.ggt(n, m); if (n == 0) return m; return this.constructor.prototype.ggt(n, m % n);

}

/**

*

Bruch kürzen

*

@private

/

Bruch.prototype

kuerzen

= function() {

/* Nicht-statischer Aufruf der statischen Methode ist in einer nicht- statischen Methode möglich. */

var t = this.ggt(this.zaehler, this.nenner); this.zaehler /= t; this.nenner /= t;

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(6, 8); console.log( '%s', bruch); /* 'Bruch 3/4' */ console.log( '%s', Bruch.prototype.ggt(6,8)); /* 2 */ console.log( '%s', bruch.ggt(6,8)); /* 2 */

In statischen Methoden bietet sich auch das Betreten des Namensraumes this.constructor.prototype an:

Seite 27

Bruch.prototype.ggt = function(m, n) { with ( this.constructor.prototype ) { if (m < n) return ggt(n, m); if (n == 0) return m; return ggt(n, m % n);

}

}

Nachteilig daran ist, wenn nachträglich eine Eigenschaft m dem Objekt Bruch.prototype hinzugefügt wird, dass dann in diesem Code Bruch.prototype.m verwendet wird statt der Variable m des Aktivierungsobjektes. Daher ist die folgende Möglichkeit der Kurzschreibweise besser:

Bruch.prototype.ggt = function(m, n) { var _static = this.constructor.prototype; if (m < n) return _static.ggt(n, m); if (n == 0) return m; return _static.ggt(n, m % n);

}

5.4 Objekte kopieren

Eine Möglichkeit Objekte zu kopieren sind Kopierkonstruktoren. Wie der Name schon verrät dienen Kopierkonstruktoren dazu, neue Objekte einer Klasse durch Kopieren anzulegen. Prinzipiell sind sie ganz normale Konstruktoren, die nur ein Objekt als Parameter erwarten, das zur gleichen Klasse gehört wie das zu erzeugende Objekt. In JavaScript gibt es nur einen Konstruktor. Der kann jedoch überladen werden. Das bedeutet, dass immer wenn nur ein Ojekt als Parameter übergeben wird und dieses Objekt auch den gleichen Konstruktor hat wie das zu erzeugende Objekt, der Konstruktor zum Kopieren des Objektes dienen soll. Das folgende Beispiel erzeugt einen Konstruktor und einen Kopierkonstruktor für die Klasse Bruch.

var Bruch = function(zaehler, nenner) { if (arguments.length == 1 && zaehler instanceof this.constructor) { var orig = zaehler; this.zaehler = orig.zaehler; this.nenner = orig.nenner; } else { this.zaehler = zaehler || 0; this.nenner = nenner || 1;

}

}

Bruch.prototype.toString = function() { return 'Bruch ' + this.zaehler + '/' + this.nenner;

}

var bruch = new Bruch(2, 3); var clone = new Bruch(bruch);

console.log( '%s', bruch); console.log( '%s', clone);

Beim Erstellen eigener Konstruktoren sollte man immer den Fall abfangen, dass genau ein Objekt als Parameter übergeben wird, das der gleichen Klasse angehört. Dann soll der Konstruktor zum Kopieren des Objektes

Seite 28

dienen. Die Konstruktoren von Standardobjekten in JavaScript haben diese Eigenschaft. Dadurch kann auch ein rekursives Kopieren von Objekten unterstützt werden.

Seite 29

6

Vererbung

6.1 Vererbung durch Konstruktoraufruf

Die einfachste Form der Vererbung ist beim Konstruktoraufruf den Konstruktor der Klasse aufzurufen, von dem geerbt werden soll:

var Fahrzeug = function() { this.type = 'Fahrzeug'; this.raeder = 4;

}

Fahrzeug.prototype.toString = function() {

var s = 'Typ : ' + this.type; s += ', Räder : ' + this.raeder; return s;

}

// Auto-Konstruktor var Auto = function() { Fahrzeug.call(this); this.type = 'Auto';

}

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', 'Typ : ' + f.type + ', Räder : ' + f.raeder); console.log( '%s', 'Typ : ' + a.type + ', Räder : ' + a.raeder);

console.log( '%s', f); console.log( '%s', a);

console.log( '%s', f instanceof Fahrzeug); /* true */ console.log( '%s', a instanceof Fahrzeug); /* false */ console.log( '%s', a instanceof Auto); /* true */

Durch den Aufruf des Konstruktors Fahrzeug erhält auch Auto alle echten Eigenschaften von Fahrzeug. Die Methode toString() wird dadurch nicht vererbt, da sie nur in Objekten zu finden ist, die mit dem Operator new und der Konstruktorfunktion Fahrzeug angelegt wurden. Außerdem verweigert der instanceof-Operator noch die Zugehörigkeit des Objektes a als eine Instanz von Fahrzeug. Beides wird erst durch die prototypbasierte Vererbung ermöglicht.

6.2 Prototypbasierte Vererbung

Um auch die Eigenschaften des Prototyps der Vaterklasse zu vererben, muss man den Prototyp auf ein mit dem new-Operator erzeugtes Objekt der Vaterklasse setzen. Dadurch entsteht dann eine prototypbasierte Vererbungskette:

Seite 30

var Fahrzeug = function() { this.type = 'Fahrzeug'; this.raeder = 4;

}

Fahrzeug.prototype.toString = function() { var s = 'Typ : ' + this.type; s += ', Räder : ' + this.raeder; return s;

}

// Auto-Konstruktor var Auto = function() { Fahrzeug.call(this); this.type = 'Auto';

}

// Den Prototypen auf ein beliebiges Fahrzeug setzen

Auto.prototype = new Fahrzeug();

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', f);/* 'Typ : Fahrzeug, Räder : 4' */ console.log( '%s', a);/* 'Typ : Auto, Räder 4' */

console.log( '%s', f instanceof Fahrzeug); /* true */ console.log( '%s', a instanceof Fahrzeug); /* true */ console.log( '%s', a instanceof Auto); /* true */

Wenn in diesem Beispiel JavaScript im Objekt a nach einer Methode toString() sucht, wird sie nicht gefunden werden. Daher wird im Prototyp des Kontruktors danach gesucht. Auch da ist sie nicht zu finden. Erst im Prototyp dessen Konstruktors ist eine Methode toString() definiert, welche dann mit call und dem Objekt a von JavaScript aufgerufen wird. Auch der instanceof-Operator liefert durch die prototypbasierte Vererbung das gewünschte Ergebnis: a ist eine Instanz von Auto. Zu beachten ist, dass der Prototyp der abgeleiteten Konstruktorfunktion ein mit dem new-Operator erstelltes Objekt der abzuleitenden Konstruktorfunktion ist, da nur der new-Operator eine interne Referenz zwischen einem Objekt und der prototyp-Eigenschaft dessen Konstruktors herstellt. Die Verknüpfung von Konstruktorprototypen mit anderen Objekten nennt man Prototypkette, die von JavaScript durchlaufen wird, um nicht-eigene Eigenschaften oder Methode des Objektes zu finden. Ein Problem im vorangegangenen Beispiel ist, dass durch die Zuordnung des Prototypen von Auto die Konstruktoreigenschaft von a nicht mehr stimmt (siehe Seite 21 in 5.1.4 Die Eigenschaft „constructor“):

Seite 31

var Fahrzeug = function() {

}

Fahrzeug.toString = function() { return 'Fahrzeugkonstruktor';

}

// Auto-Konstruktor

var Auto = function() {

}

Auto.prototype = new Fahrzeug();

Auto.toString = function() { return 'Autokonstruktor';

}

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', f.constructor); /* 'Fahrzeugkonstruktor' */ console.log( '%s', a.constructor); /* 'Fahrzeugkonstruktor' */

Die Lösung ist, die constructor-Eigenschaft nach dem Zuweisen des Prototypen auf den richtigen Wert zu setzen:

var Fahrzeug = function() {

}

Fahrzeug.toString = function() {

return 'Fahrzeugkonstruktor';

}

// Abgeleiteter Konstruktor var Auto = function() {

}

Auto.toString = function() { return 'Autokonstruktor';

}

Auto.prototype = new Fahrzeug();

// Zuweisung des richtigen Konstruktors Auto.prototype.constructor = Auto;

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', f.constructor); /* 'Fahrzeugkonstruktor' */ console.log( '%s', a.constructor); /* 'Autokonstruktor' */

Damit wäre die prototypbasierte Vererbung komplett, wenn nicht noch ein weiteres Problem existieren würde:

Um die prototypbasierte Vererbung zu ermöglichen musste bisher immer ein Objekt der Vaterklasse mit dem new-Operator instanziiert werden, um die Verknüpfung zum Prototyp der Vaterklasse zu erstellen. Dadurch

Seite 32

wird aber immer der Konstruktor der Vaterklasse aufgerufen, was abernicht nötig ist oder sogar unerwünscht Die Lösung ist, dass zum Erzeugen des Prototypen ein Konstruktor verwendet wird, der nichts tut. Stattdessen wird dem Prototypen dieses Konstruktors der Prototyp der Vaterklasse zugewiesen. Mit Hilfe dieses Konstruktors erzeugen wir einen neuen Prototypen:

var Fahrzeug = function() {

}

Fahrzeug.toString = function() {

return 'Fahrzeugkonstruktor';

}

// Abgeleiteter Konstruktor var Auto = function() {

}

Auto.toString = function() {

return 'Autokonstruktor';

}

// Auto erbt von Fahrzeug var Prototype = new Function(); Prototype.prototype = Fahrzeug.prototype; Auto.prototype = new Prototype(); Auto.prototype.constructor = Auto;

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', f.constructor); /* 'Fahrzeugkonstruktor' */ console.log( '%s', a.constructor); /* 'Autokonstruktor' */

Um die Vererbung zu vereinfachen, kann man auch Funktionsobjekte um eine Methode inheritsOf erweitern. Da alle Funktionen als Konstruktor dienen können, wird die Konstruktorfunktion Function erweitert:

Function.prototype.inheritsOf = function(_class) { var Prototype = new Function(); Prototype.prototype = _class.prototype; this.prototype = new Prototype(); this.prototype.constructor = this;

}

Von nun an kann man z. Bsp. schreiben:

Auto.inheritsOf(Fahrzeug);

Beispiel:

Seite 33

var Fahrzeug = function() { this.type = 'Fahrzeug'; this.raeder = 4;

}

Fahrzeug.toString = function() { return 'Fahrzeugkonstruktor';

}

Fahrzeug.prototype.toString = function() { var s = 'Typ : ' + this.type; s += ', Räder : ' + this.raeder; return s;

}

// Auto-Konstruktor var Auto = function() { Fahrzeug.call(this); this.type = 'Auto';

}

Auto.toString = function() { return 'Autokonstruktor';

}

// Auto erbt von Fahrzeug Auto.inheritsOf( Fahrzeug );

var f = new Fahrzeug(); var a = new Auto();

console.log( '%s', f.constructor);/* 'Fahrzeugkonstruktor'*/ console.log( '%s', a.constructor);/* 'Autokonstruktor'*/

console.log( '%s', f);/* 'Typ : Fahrzeug, Räder : 4' */ console.log( '%s', a);/* 'Typ : Auto, Räder : 4'*/

console.log( '%s', f instanceof Fahrzeug); /* true */ console.log( '%s', a instanceof Fahrzeug); /* true */ console.log( '%s', a instanceof Auto); /* true */

8

Seite 34

7

Ausnahmen fangen und werfen

7.1 Allgemeines zu Ausnahmen

JavaScript unterstützt Ausnahmebehandlung. Ausnahmen können mit throw geworfen werden und mit einer

try

Im folgenden Beispiel soll das Anlegen eines Bruchs mit dem Nenner 0 vermieden werden. Außerdem sind nur

Ganzzahlen erlaubt.

catch - Anweisung abgefangen werden.

var Bruch = function ( zaehler, nenner )

this.zaehler = zaehler; this.nenner = nenner;

if( !this

isInt(this.nenner)

|| !this

{

isInt(this.zaehler)

){

throw new Error('Ganzzahlen als Argumente erwartet');

}

if( this.nenner == 0 ){ throw new Error('Nenner ist 0');

}

}

Bruch.prototype

isInt

= function(x){

if( x instanceof Object ){ return false;

}

var y = parseInt(x); if( isNaN(y) ) { return false;

}

return x === y ;

}

try{ var b = new Bruch(1,0);

}

catch( e ){ console.log( '%s', e.message); var b = false;

}

console.log( '%s', b );

Wird eine Ausnahme geworfen, dann wird sie vom catch-Block gefangen und das geworfenen Objekt übergeben. Anderenfalls wird der catch-Block nicht ausgeführt. Wird eine Ausnahme in der Anwendung nicht gefangen, dann wird die JavaScript -Anwendung beendet.

7.2 Der „Error“-Konstruktor

Die Konstruktorfunktion Error ist ein vordefiniertes JavaScript -Objekt mit folgenden Eigenschaften:

Seite 35

Eigenschaft

Beschreibung

name

Name der Ausnahme

message