2 JavaScript-Grundlagen
Wir wollen in diesem Buch keine Einführung in JavaScript geben. Es gibt bereits einige sehr gute Bücher, die eine solche Einführung zum Ziel haben, beispielsweise „Eloquent JavaScript“ von Marijn Haverbeke (http://eloquentjavascript.net/), „JavaScript: The Good Parts“ von Douglas Crockford oder „JavaScript für Enterprise-Entwickler“ von Oliver Ochs. Auch für fortgeschrittene Fragestellungen beim Einsatz von JavaScript gibt es gute Bücher, wie beispielsweise „JavaScript Patterns“ von Stoyan Stefanov. Wir möchten in diesem Kapitel in einer losen Sammlung auf die für unsere Beispiele relevanten Grundlagen eingehen und diese kurz erläutern.
2.1 Objekte und Objektliterale
Objekte in JavaScript sind im wesentlichen Container für Properties mit ihren Werten. Im Grunde genommen sind JavaScript-Objekte also Hash Maps. Objekte können in JavaScript wie in Listing 2.1 gezeigt erzeugt und um Properties erweitert werden.
1 var customer = new Object();
2 customer.firstName = "John";
3 customer.lastName = "Doe";
Listing 2.1: Erzeugen und initialisieren von Objekten in JavaScript
In Zeile 1 erzeugen wir ein neues Objekt mithilfe der Konstruktorfunktion Object und dem Operator new. In den Zeilen 2 und 3 erweitern wir dieses neue Objekt um zwei Properties und initialisieren diese. Eine alternative Möglichkeit, ein „leeres“ Objekt zu erzeugen, ist die Verwendung des entsprechenden Objekt-Literals. Die erste Zeile in Listing 2.1 könnte auch lauten: var customer = {};. Die Syntax für Objektliterale unterstützt auch das Definieren von Properties. Die Werte der Properties können dabei wieder Objektliterale sein. Die Syntax kann darüber hinaus mit der Verwendung von [ und ] für Listen-Literale kombiniert werden. Ein Beispiel zeigt Listing 2.2.
var customer = {
firstName: "John",
lastName: "Doe",
address: {
street: "Some Street",
city: "Some City",
zipcode: "12345"
},
contacts: [ { type: "home", value: "000-1234" },
{ type: "mobile", value: "111-9876" } ]
};
Listing 2.2: Geschachtelte Objektliterale und Objektlisten
Der Wert einer Objekt-Property kann auch eine Funktion sein. Mehr dazu in Abschnitt 2.2.3.
2.2 Funktionen
In JavaScript sind Funktionen bekanntlich „First Class Citizens“. Sie sind Objekte vom Typ function. Sie können Variablen und Objekt-Properties zugewiesen werden und als Argument an andere Funktionen übergeben werden. Ferner haben Funktionen wie alle Objekte Properties und können auch um selbstdefinierte Properties erweitert werden. In Kapitel 6 werden wir beispielsweise eine Property $inject auf Funktionen definieren, um damit anzugeben, welche Abhängigkeiten der Funktion bei ihrem Aufruf übergeben werden sollen. Diese Property wird dann zur Laufzeit von einem Dependency-Injection-Framework ausgewertet. Mehr dazu in Kapitel 6.
2.2.1 Codeblöcke
Werden Funktionen an andere Funktionen übergeben, dienen sie häufig nur als „Container“ für einen Codeblock, der von der aufgerufenen Funktion dann zur gegebenen Zeit ausgeführt wird. Solche Funktionen werden häufig nicht explizit definiert, sondern als anonymer Funktionsausdruck übergeben. Listing 2.3 zeigt ein Beispiel, in dem eine anonyme Funktion an eine globale Funktion it des Testframeworks Jasmine (siehe Kapitel 4) übergeben wird. Sie wird vom Testframework aufgerufen, sobald dieses den Test ausführt. Die anonyme Funktion enthält also den Code, der den eigentlichen Test ausmacht.
it("should be true", function() {
expect(1 + 1).toBe(2);
});
Listing 2.3: Anonyme Funktionen als Codeblöcke
2.2.2 Callbacks
Callbacks liefern einen weiteren Anwendungsfall für das Übergeben von Funktionen an andere Funktionen. Callback-Funktionen werden aufgerufen, sobald ein bestimmtes Ereignis auftritt. Einer fiktiven Funktion loadData könnten beispielsweise als Argumente ein URL als String, eine Success-Callback-Funktion success und eine Error-Callback-Funktion error übergeben werden. Sobald loadData die Daten erfolgreich vom angegebenen URL geladen hat, ruft sie success auf und übergibt die geladenen Daten. Wenn ein Fehler auftritt, ruft sie error auf und übergibt zum Beispiel einen Error-Code. Auch Callback-Funktionen werden häufig als anonyme Funktionsausdrücke übergeben. Listing 2.4 zeigt den Aufruf unserer fiktiven Funktion loadData mit zwei anonymen Funktionsausdrücken.
loadData("http://some.host:1234/some_path",
function(data) {
// do something with the loaded data...
}, function(errorCode) {
// handle error...
});
Listing 2.4: Anonyme Funktionsausdrücke als Callbacks
2.2.3 Methoden
Eine Funktion kann auch der Wert einer Objekt-Property sein. Diese Funktionen können dann als Methoden ihrer Objekte aufgerufen werden und haben in diesem Fall über den Bezeichner this Zugriff auf „ihr“ Objekt und damit auf dessen Properties. Der fiktive Code aus Listing 2.5 verdeutlicht dies.
var customer = {
firstName: "John",
lastName: "Doe",
fullName: function() {
return this.firstName + this.lastName;
}
};
customer.fullName(); // -> John Doe
Listing 2.5: Funktionen als Methoden
Wichtig ist in diesem Zusammenhang, dass eine Funktion nur dann über this Zugriff auf „ihr“ Objekt hat, wenn sie als Methode aufgerufen wird. Wird eine Funktion beispielsweise als Callback-Funktion verwendet, so findet dieses Binding nicht statt. Der Code aus Listing 2.6 funktioniert daher nicht! Ein gebräuchliches Idiom zur Vermeidung dieses Fehlers ist das Speichern von this in einer lokalen Variablen, die dann meist self oder that benannt wird, siehe Listing 2.7.
var customer = {
contacts: [],
loadContacts: function() {
load(someUrl, function(result) {
this.contacts = result; // WON'T WORK!!!
});
}
};
Listing 2.6: Fehlerhafte Verwendung von „this“
var customer = {
contacts: [],
loadContacts: function() {
var self = this;
load(someUrl, function(result) {
self.contacts = result;
});
}
};
Listing 2.7: Korrekte Verwendung von „this“
2.2.4 Sofort ausgeführte Funktionsausdrücke
Anonyme Funktionen können auch in Form einer so genannten „Immediate Invoked Function Expression“ (IIFE) sofort ausgeführt werden. Dieses Konstrukt wird verwendet, um einen dedizierten Gültigkeitsbereich für die Deklaration von Variablen zu erzeugen und damit die „Verschmutzung“ des globalen Namensraums einer JavaScript-Laufzeitumgebung zu vermeiden. Listing 2.8 zeigt die Syntax einer IIFE. Die Anwendung wird im Abschnitt 2.4 beschrieben.
(function() {
// do something in a local function scope
})(); // will execute the anonymous function immediately
Listing 2.8: Syntax für einen sofort ausgeführten Funktionsausdruck
2.3 Namespaces
JavaScript verfügt über keine in der Sprachsyntax eingebauten Mechanismen zur Steuerung der Sichtbarkeit und Gültigkeit von Deklarationen. Die einzige Ausnahme ist der Function Scope. Variablen, die in einer Funktion deklariert werden, sind nur in dieser Funktion und in Funktionen, die innerhalb dieser Funktion definiert werden, sichtbar. Gleiches gilt für die Funktionen, die in einer Funktion definiert werden. Alles andere erweitert das globale Objekt, das eine JavaScript-Laufzeitumgebung einem Skript zur Verfügung stellt, und ist somit global sichtbar. Damit ist es eine Herausforderung, diesen globalen Namensraum nicht durch applikationsspezifische Objekte zu verschmutzen. Ein einfaches und weit verbreitetes Muster zur Lösung dieses Problems ist die Verwendung eines dedizierten Namespace-Objekts, das dann alle weiteren applikationsspezifischen Objekte aufnimmt. Für unsere Beispielapplikation könnten wir ein solches Namespace-Objekt einfach durch die Anweisung var rylc = {}; erzeugen. Wir müssen nur sicherstellen, dass diese Anweisung die erste applikationsspezifische Codezeile ist, die beim Laden der Applikation durch den Browser ausgeführt wird.
2.4 Module
Mithilfe sofort ausgeführter Funktionsausdrücke können wir eine einfache Form von Modulen implementieren, die nur bestimmte Objekte publik machen. Listing 2.9 verdeutlicht das an dem Beispiel eines Moduls utils, das über unser Namespace-Objekt rylc bestimmte Utility-Funktionen zur Verfügung stellt.
1 (function(rylc) {
2 // --- Private Implementation ---
3 function parseSimpleDate(dateAsString) {
4 // ...
5 }
6 function formatSimpleDate(Date) {
7 // ...
8 }
9 // more private stuff ...
10
11 // --- Public API ---
12 rylc.utils = {
13 parseSimpleDate: parseSimpleDate,
14 formatSimpleDate: formatSimpleDate
15 }
16 })(window.rylc);
Listing 2.9: Beispiel für ein einfaches JavaScript-Modul
Wir verwenden...