JavaScript 1.7 ist ein Update für die Sprache, womit mehrere neue Features wie Generatoren, Iteratoren, Array Comprehensions, let
-Ausdrücke und Destructing Assignments hinzugefügt wurden. Es enthält außerdem alle Features von JavaScript 1.6.
Die Unterstützung von JavaScript 1.7 beginnt mit Firefox 2.
Die in diesem Artikel bereitgestellten Code-Beispiele können in der JavaScript-Shell getestet werden. Lesen Sie Introduction to the JavaScript shell, um mehr über die Benutzung der Shell zu lernen.
Verwendung von JavaScript 1.7
Bei einigen der neuen Features von JavaScript 1.7 ist es wichtig, die Version anzugeben, wenn man sie einsetzen möchte. Bei HTML oder XUL z. B. wie folgt:
Bei Verwendung der JavaScript-Shell gibt man die Version über den Parameter -version 170
auf der Kommandozeile an oder greift auf die Funktion version()
zurück:
Neue Features, bei denen die neuen Schüsselwörtern wie yield
und let
zum Einsatz kommen, setzen die Versionsangabe voraus, da bei existierendem Code diese Schlüsselwörter eventuell bereits als Bezeichner für Variablen oder Funktionen verwendet werden. Bei anderen Features, die keine neuen Schlüsselwörter einführen (Destructing Assignments und Array Comprehensions) kann auf die die Versionsangabe verzichtet werden.
Generatoren und Iteratoren (siehe Iteratoren und Generatoren)
Bei der Entwicklung von Code, in dem iterative Algorithmen zum Einsatz kommen (wie z. B. Iterieren über Listen, XML-Nodes, Datenbank-Ausgaben; oder wiederholtes Verarbeiten desselben Datensatzes) gibt es oft Status-Variablen, deren Werte während des Verbeitungsprozesses verändert werden müssen. Tradiotionell müssten diese Werte dann über eine Callback-Funktion bezogen werden.
Generatoren
Hier ein Beispiel mit einem iterativer Algorithmus für die Berechnung von Fibonacci-Zahlen:
Bei diesem Code kommt eine Callback-Routine zum Einsatz, um bei jedem iterativen Schritt des Algorithmus bestimmte Operationen durchzuführen. In diesem Fall wird jede Fibonacci-Zahl einfach auf der Konsole ausgegeben.
Generatoren und Iteratoren arbeiten zusammen, um dies besser umzusetzen. Das folgende Beispiel berechnet ebenfalls Fibonacci-Zahlen, diesmal allerdings unter Verwendung eines Generators:
function fib() { var i = 0, j = 1; while (true) { yield i; var t = i; i = j; j += t; } } var g = fib(); for (var i = 0; i < 10; i++) { console.log(g.next()); }
Bei der Funktion fib()
, welche das Schlüsselwort yield
enthält, handelt es sich um einen Generator. Beim Aufruf dieser Funktion werden ihre formalen Parameter an akteulle Argumente gebunden, aber der Code nicht ausgeführt, sondern stattdessen ein Generator-Iterator zurückgegeben. Bei jedem Aufruf der next()
-Methode des Generator-Iterators wird dann ein weiterer Iterationsschritt durchgeführt. Der Rückgabewert jedes Durchlaufs ist der Wert des Operanden von yield
. Man kann sich yield
ungefähr wie eine spezielles return
für Generator-Iterator-Objekte vorstellen, welches die Begrenzung für jeden Iterationsschritt festlegt. Bei jedem Aufruf von next()
wird die Ausführung dann bei der Anweisung nach yield
fortgesetzt.
Ein Generator-Iterator wird durchlaufen, indem die Methode next()
wiederholt aufgerufen wird, solange bis schließlich das gewünscht Ergebnis erreicht bzw. die Bedingung erfüllt ist. Bei dem Beispiel mit den Fibonacci-Zahlen wird also wiederholt g.next()
aufgerufen, bis die gewünschte Anzahl von Fibonacci-Zahlen erzeugt wurde.
Fortsetzen eines Generators an einem bestimmten Punkt
Sobald ein Generator einmal durch den Aufruf der next()
-Methode gestartet wurde, kann über die Methode send()
ein beliebiger Wert an den Generator übergeben werden. Dieser Wert wird dann so interpriert, als wäre es der Wert des letzten yield
. Der Generator gibt dann den Operanden des darauf folgenden yield
zurück.
Ein Generator lässt sich nicht direkt an einem bestimmten Punkt starten, sondern nur über die Methode next()
. Erst nach dem next()
-Aufruf kann über send()
ein Wert gesetzt werden.
send(undefined)
ist äquivalent zum Aufruf von next()
. Jeder Start eines neuen Generator über send()
mit einem anderen Wert als undefined resultiert jedoch in einem TypeError
-Ausnahmefehler.Ausnahmen bei Generatoren
Man kann einen Generator dazu bringen, eine Ausnahme auszuwerfen, indem man seine throw()
-Methode aufruft und ihr den Wert übergibt, der ausgeworfen werden soll. Diese Ausnahme wird unter Einbeziehung des Kontext des aktuellen Unterbrechung ausgeworfen, so als wäre das yield
des aktuellen Durchlaufs eine Anweisung throw Wert
.
Trifft die Ausführung während der Verarbeitung der ausgeworfenen Ausnahme auf kein yield
, dann verbreitet sich die Ausnahme bis nach oben durch den Aufruf von next()
und nachfolgende Aufrufe von next()
lösen eine StopIteration
-Ausnahme aus.
Schließen eines Generators
Generatoren besitzen eine Methode close()
, welche den Generator zwingt, sich selbst zu schließen. Eine solchen Schließung wirkt sich wie folgt aus:
- Jede
finally
-Klausel, die in der Generator-Funktion aktiv ist, wird ausgeführt. - Wird in einer
finally
-Klausel eine andere Ausnahme alsStopIteration
ausgeworfen, so wird diese an die Funktion weitergeleitet, dieclose()
aufgerufen hat. - Der Generator terminiert.
Generator-Beispiel
Dieser Code arbeitet mit einem Generator, der alle 100 Schleifendurchläufe etwas "abwirft" (yield = abwerfen):
var gen = generator(); function driveGenerator() { if (gen.next()) { window.setTimeout(driveGenerator, 0); } else { gen.close(); } } function generator() { while (i < something) { /** stuff **/ ++i; /** 100 loops per yield **/ if ((i % 100) == 0) { yield true; } } yield false; }
Iteratoren
Ein Iterator ist ein spezielles Objekt, das es erlaubt über Daten zu iterieren.
Bei der normalen Benutzung sind Iterator-Objekte "unsichtbar"; man braucht sie nicht explizit anzusprechen, sondern setzt stattdessen for...in
und for each...in
Anweisungen ein, um über Schlüssel und/oder Werte von Objekten zu iterieren.
var objectWithIterator = getObjectSomehow(); for (var i in objectWithIterator) { console.log(objectWithIterator[i]); }
Für die Implementierung eines eigenen Iterator-Objekts oder zur direkten Manipulation von Iteratoren sollte man über die next()
-Methode, die StopIteration
-Ausnahme und die __iterator__
-Methode bescheid wissen.
Ein Iterator für ein Objekt erzeugt man mit dem Aufruf von Iterator(objectname)
; . Der Iterator kann über die Methode __iterator__
gefunden werden; ist diese Methode nicht vorhanden, handelt es sich um einen Default-Iterator. Ein Default-Iterator wirft über yield
die Eigenschaften des Objekts ab. Möchte man stattdessen einen selbstdefinierten Iterator einsetzen, überschreibt man die Methode __iterator__
, damit diese eine Instanz des selbstbefinierten Iterators zurückgibt. Um den Iterator eines Objekts über ein Skript abzufragen, sollte man auf Iterator(obj)
statt direkt auf __iterator__
zurückgreifen, da ersteres für Arrays funktioniert und letzteres nicht.
Nach der Erzeugung des Iterators kann man einfach über die Methode next()
das nächste Element abfragen. Sind keine Daten mehr vorhanden, wird eine StopIteration
-Ausnahme ausgeworfen.
Hier ein einfaches Beispiel für die direkte Manipulation eines Iterators:
var obj = {name:"Jack Bauer", username:"JackB", id:12345, agency:"CTU", region:"Los Angeles"}; var it = Iterator(obj); try { while (true) { print(it.next() + "\n"); } } catch (err if err instanceof StopIteration) { print("End of record.\n"); } catch (err) { print("Unknown error: " + err.description + "\n"); }
Die Ausgabe dieses Codes ist wie folgt:
name,Jack Bauer username,JackB id,12345 agency,CTU region,Los Angeles End of record.
Optional kann man bei der Erstellung eines Iterators einen booleschen Wert als zweiten Parameter angeben, womit festgelegt wird, ob nur der jeweilge Schlüssel (statt Schlüssel und Wert) beim Aufruf der next()
-Methode zurückgegeben werden soll. Kommt eine benutzerdefinierte __iterator__
-Funktionen zum Einsatz, wird der Wert als einziges Argument an diese übergeben. Würde man beim Beispiel oben die Anweisung var it = Iterator(obj);
in var it = Iterator(obj, true);
ändern, dann würde man folgende Ausgabe erhalten:
name username id agency region End of record.
In beiden Fällen kann die Reihenfolge der Datenausgabe - je nach Implemetierung - variieren. Es gibt keine Garantie für eine bestimmte Anordung der ausgegebenen Daten.
Iteroren sind nützlich, um sich über die Daten in einem Objekt zu informieren. Es kann z.B. vorkommen, dass Objekte Daten enthalten, mit denen man gar nicht rechnet, z.B. weil Daten erhalten bleiben sollen, die von der Applikation nicht erwartet werden.
Array Comprehensions (siehe Array comprehensions)
Array Comprehensions nutzen Generatoren für die effiziente Initialisierung von Arrays. Zum Beispiel:
function range(begin, end) { for (let i = begin; i < end; ++i) { yield i; } }
range()
ist ein Generator, der alle Werte zwischen begin und end zurückgibt. Nach der Definition kann der Generator wie folgt benutzt werden:
var ten_squares = [i * i for each (i in range(0, 10))];
Damit wird ein Array ten_squares vorinitialisiert, welches die Quadratzahlen der Werte von 0
bis 9
enthält.
Bei der Initialisierung können beliebige Bedingungen angegeben werden. Mit folgendem Code wird z. B. ein Array initialisiert, welches die geraden Zahlen zwischen 0 und 20 enthält:
var evens = [i for each (i in range(0, 21)) if (i % 2 == 0)];
Bei Versionen vor JavaScript 1.7 würde man dies ungefähr so umsetzen:
var evens = []; for (var i=0; i <= 20; i++) { if (i % 2 == 0) evens.push(i); }
Die Array Comprehension ist nicht nur kompakter, sondern auch lesbarer, sobald man das Konzept einmal verstanden hat.
Regeln für die Sichtbarkeit (Scope)
Array Comprehensions sind durch einen impliziten Block abgegrenzt, der alles, was zwischen den eckige Klammern steht, einschließt; inklusive impliziter let
-Deklarationen.
Block-Begrenzungen mit let
(siehe let
Statement)
Es gibt verschiedene Möglichkeiten, wie mit let
Blockbegrenzungen von Daten und Funktionen verwaltet werden können.
- Die
let
-Anweisung ermöglicht die Zuweisung von Werten an Variablen innerhalb des Sichtbarkeitsbereichs eines Blocks, ohne dass die Werte gleichnamiger Variablen außerhalb dieses Blocks beeinflusst werden. - Mit dem
let
-Ausdruck können Variablen erzeugt werden, die nur für einen einzelnen Ausdruck gelten. - Die
let
-Definition definiert Variablen, dessen Sichtbarkeitsbereich durch den Block in dem sie definiert wurden, bestimmt wird. Diese Syntax ist der vonvar
sehr ähnlich. - Außerdem können mit
let
Variablen erzeugt werden, die nur innerhalb des Kontext einer for-Schleife sichtbar sind.
let-Anweisung
Die let-Anweisung ermöglicht die Erzeugung eines lokalen Sichtbarkeitsbereichs für Variablen. Eine oder mehrere Variablen werden an die lexikalische Begrenzung eines einzelnen Blocks gebunden. Andernfalls ist es exakt das Gleiche wie eine Block-Anweisung. Der Sichtbarkeitsbereich einer mit var
deklarierten Variablen ist allerdings - auch wenn sie innerhalb der let
-Anweisung deklariert wurde - noch immer der Sichtbarkeitsbreich der Funktion. Mit var
deklarierten Variablen verhalten sich also exakt gleich, wenn sie innerhalb oder außerhalb der let
-Anweisung deklariert werden - sie befinden sich im Sichtbarkeitsbereich der Funktion.
Zum Beispiel:
var x = 5; var y = 0; let (x = x+10, y = 12) { console.log(x+y); // 27 } console.log(x + y); // 5
Die Regeln für die Blockbegrenzung sind dieselben wie bei jedem anderen Block bei JavaScript. Mit Hilfe der let
-Deklarationen können eigene lokale Variablen bestimmt werden.
let
-Anweisungen sind die Klammern nach let nicht optional, sondern müssen unbedingt angegeben werden; das Weglassen der Klammern resultiert in einem Syntaxfehler.Regeln für den Sichtbarkeitsbereich (Scope)
Der Sichtbarkeitsbereich von Variablen, die mit let
deklariert werden, ist der let
-Block selbst, innere verschachtelte Blöcke eingeschlossen, solange darin keine Variablen mit dem gleichen Namen definiert werden.
let
-Ausdrücke
Mit let
kann die Sichtbarkeit von Variablen auf einzelne Ausdrücke beschränkt werden:
var x = 5; var y = 0; console.log(let(x = x + 10, y = 12) x + y); console.log(x + y);
Ausgabe:
27 5
In diese Fall ist der Sichtbarkeitsbereich für die Variablen x und y bei den Zuweisungen der Werte x+10
und 12
auf den Ausdruck x + y + "<br>\n"
beschränkt.
Regeln für die Sichtbarkeit (Scope)
Gegeben ist ein let
-Ausdruck:
let (decls) expr
Dann wird um expr herum ein impliziter Block erzeugt.
let
-Definitionen
Das Schlüsselwort let
kann auch für die Definition von Variablen in einem Block verwendet werden.
if (x > y) { let gamma = 12.7 + y; i = gamma * x; }
Bei Erweiterungen kann man let
für die Benennung von pseudo-namespaced Code einsetzen.
(Siehe Security best practices in extensions.)
let Cc = Components.classes, Ci = Components.interfaces;
Falls mit innerere Funktionen gearbeitet wird, können let
-Anweisungen, -Ausdrücke und -Definitionen mitunter für saubereren Code sorgen:
var list = document.getElementById("list"); for (var i = 1; i <= 5; i++) { var item = document.createElement("LI"); item.appendChild(document.createTextNode("Item " + i)); let j = i; item.onclick = function (ev) { alert("Item " + j + " is clicked."); }; list.appendChild(item); }
Der Code funktioniert wie erwartet, da die fünf Instanzen der (anonymen) inneren Funktion mit fünf unterschiedlichen Instanzen der Variablen j
arbeiten. Der Code würde so nicht funktionieren, wenn man var
statt let
verwenden oder die Variable j
entfernen und einfach die Variable i
in der inneren Funktion benutzen würde.
Regeln für den Sichtbarkeitsbereich (Scope)
Durch let
deklarierte Variablen sind in dem Block in dem sie definiert wurden sichtbar, als auch in jedem inneren Block, sofern sie darin nicht neu definiert wurden. Hier verhält sich let
also sehr ähnlich wie var
. Der größte Unterschied besteht darin, dass bei var
-Variablen der Sichtbarkeitsbereich die ganze einschließende Funktion ist:
function varTest() { var x = 31; if (true) { var x = 71; // dieselbe Variable! alert(x); // 71 } alert(x); // 71 } function letTest() { let x = 31; if (true) { let x = 71; // andere Variable! alert(x); // 71 } alert(x); // 31 }
Der Ausdruck auf der rechten Seite des Gleichzeichens befindet sich - anders als bei let
-Ausdrücken und let
-Anweisungen - innerhalb des Blocks:
function letTests() { let x = 10; // let-Anweisung let (x = x + 20) { alert(x); // 30 } // let-Ausdruck alert(let (x = x + 20) x); // 30 // let-Definition { let x = x + 20; // x evaluiert hier zu undefined alert(x); // undefined + 20 ==> NaN } }
Beim Top-Level von Skripten und Funktionen verhält sich let
exakt wie var
. Zum Beispiel:
var x = 'global'; let y = 'global'; console.log(this.x); console.log(this.y);
Die Ausgabe ist hier zweimal "global".
let
-Definitionen ändern sich voraussichtlich bei ES6.let
-Variablen in for
-Schleifen
Mit Hilfe von let
können Variablen lokal an den Sichtbarkeitsbereich einer for
-Schleife gebunden werden. Der Unterscheid zu im Kopf der Schleife definierten var
-Variablen, besteht darin, dass diese innerhalb der ganzen Funktion sichtbar sind.
var i=0; for ( let i=i ; i < 10 ; i++ ) console.log(i); for ( let [name,value] in Iterator(obj) ) console.log("Name: " + name + ", Value: " + value);
Regeln für den Sichtbarkeitsbereich (Scope)
for (let expr1; ausdr2; ausdr3) anweisung
Bei diesem Beispiel sind ausdr2, ausdr3, und anweisung in einem impliziten Block eingeschlossen. Dieser enthält die durch let expr1
deklarierte lokale Variable. Die Verwendung wurde bereits im vorherigen Beispiel demonstriert.
for (let ausdr1 in ausdr2) anweisung for each(let ausdr1 in ausdr2) anweisung
In beiden Fällen existieren implizite Blöcke, welche die jeweilige anweisung einschließen. Die erste Schleifenform ist ebenfalls im Code-Beispiel oben demonstiert.
Destructuring Assignment (Merge into own page/section)
Destructuring Assignment ermöglicht das Extrahieren von Daten aus Arrays oder Objekten mit einer ähnlichen Syntax wie bei Array- oder Objekt-Literalen.
Mit den Ausdrücken für Objekt- und Array-Literalen lassen sich auf einfache Weise passende Datenzusammenstellungen erzeugen. Nachdem diese einmal erzeugt wurden, kann beliebig darauf zugegriffen werden. Sie können sogar von Funktionen zurückgegeben werden.
Ein sehr nützlicher Einsatzzweck für Destructuring Assignment ist das Einlesen einer kompletten Struktur mit einer einzelnen Anweisung. Zudem gibt es sehr viele weitere Möglichkeiten, wie Destructuring Assignment sinnvoll eingesetzt werden kann, wie die folgenden Beispiele zeigen.
Die Einsatzmöglichkeiten sind ähnlich wie bei den Sprachen Python und Perl.
Beispiele
Destructuring Assignment lässt sich am besten anhand von Beispielen erklären. Im Folgenden also ein paar Beispiele, womit man sich mit Destructuring Assignments vertraut machen kann.
Vermeidung von temporären Variablen
Mit Hilfe von Destructuring Assignment lasse sich z. B. auf einfache Weise Werten vertauschen:
var a = 1; var b = 3; [a, b] = [b, a];
Nach der Ausführung dieses Code hat b den Wert 1 und a den Wert 3. Ohne den Einsatz von Destructuring Assignment benötigt man für das Vertauschen von Werten eine temporäre Variable (oder bei einigen Low-Level Sprachen den XOR-swap Trick).
Darüber hinaus lassen sich auch drei oder mehr Werte rotieren:
var a = 'o', b = "<span style='color:green;'>o</span>", c = 'o', d = 'o', e = 'o', f = "<span style='color:blue;'>o</span>", g = 'o', h = 'o'; for ( var lp=0; lp < 40; lp++ ) { [a, b, c, d, e, f, g, h] = [b, c, d, e, f, g, h, a]; document.write(a+''+b+''+c+''+d+''+e+''+f+''+g+''+h+''+"<br />"); }
Dieser Code erzeugt eine farbenfrohe Ausgabe, welche die Rotation der Variablen zeigt.
Um noch einmal auf das Beispiel mit dem Fibonacci-Generator zurückzukommen: Auf die temporäre Variable "t" können wir nun verzichten, indem wir die Berechnung der Werte von "i" und "j" in einer einzelnen Gruppenzuweisungsanweisung durchführen:
function fib() { var i = 0, j = 1; while (true) { yield i; [i, j] = [j, i + j]; } } var g = fib(); for (let i = 0; i < 10; i++) print(g.next());
Rückgabe von mehreren Werten
Dank Destructuring Assignment können Funktionen mehrere Werte zurückgeben. Natürlich könnten wir auch ein Array als Rückgabewert verwenden, durch Destructuring Assignment gewinnt man jedoch einen Grad an Flexibilität:
function f() { return [1, 2]; }
Wie man hier sehen kann, ist der Rückgabewert eine Array-ähnliche Notation, wobei alle Werte in den eckigen Klammern eigeschlossen sind. Auf diese Weise lassen sich beliebig viele Werte zurückgeben. Bei diesem Beispiel gibt f()
die Werte [1, 2]
zurück.
var a, b; [a, b] = f(); console.log("A is " + a + " B is " + b);
Die Anweisung [a, b] = f()
weist die Rückgabewerte der Funktion den eingeklammerten Variablen zu: a bekommt den Wert 1 und b den Wert 2.
Die Rückgabe in Form eines Arrays ist ebenfalls möglich:
var a = f(); console.log("A is " + a);
In diesem Fall ist A ein Array, welches die Werte 1 und 2 enthält.
Über Objekte iterieren
Mit Hilfe von destructuring assignment kann man auch auf die Daten eines Objekts zugreifen:
let obj = { width: 3, length: 1.5, color: "orange" }; for (let [name, value] in Iterator(obj)) { console.log("Name: " + name + ", Value: " + value); }
Bei desem Code wird über alle Schlüssel/Wert-Paare im Objekt obj iteriert und entsprechend der Name und Wert ausgegeben:
Name: width, Value: 3 Name: length, Value: 1.5 Name: color, Value: orange
Der Iterator()
um obj
herum wird bei JavaScript 1.7 nicht benötigt; bei JavaScript 1.8 jedoch schon. Damit wird Destructuring Assignment in Verbindung mit Arrays ermöglicht (siehe Bug 366941).
Iterieren über Werte von Objekten in einem Array
Nachfolgend ein Beispiel, wie man über Objekte in einem Array iterieren und bestimmte Daten herausziehen kann:
var people = [ { name: "Mike Smith", family: { mother: "Jane Smith", father: "Harry Smith", sister: "Samantha Smith" }, age: 35 }, { name: "Tom Jones", family: { mother: "Norah Jones", father: "Richard Jones", brother: "Howard Jones" }, age: 25 } ]; for each (let {name: n, family: { father: f } } in people) { console.log("Name: " + n + ", Father: " + f); }
Hierbei wird das Array people durchlaufen. Für jedes darin enthaltene Objekt wird der name
in n
und family.father
in f
gespeichert und anschließend ausgegeben:
Name: Mike Smith, Father: Harry Smith Name: Tom Jones, Father: Richard Jones
Herausziehen von Feldern aus Objekten, die als Funktionsparameter übergeben werden
function userId({id}) { return id; } function whois({displayName: displayName, fullName: {firstName: name}}) console.log(displayName + " is " + name); } var user = {id: 42, displayName: "jdoe", fullName: {firstName: "John", lastName: "Doe"}}; console.log("userId: " + userId(user)); whois(user);
Hier werden die Eigenschaften id
, displayName
und firstName
aus dem Objekt user
herausgezogen und ausgegeben.
Ignorieren von Rückgabewerten
Werte, an denen man nicht interessiert ist, kann man einfach ignorieren und auslassen:
function f() { return [1, 2, 3]; } var [a, , b] = f(); console.log("A is " + a + " B is " + b);
Nach der Ausführung ist a = 1 und b = 3. Der Wert 2 wird ignoriert. Auf diese Weise kann man einzelne oder auch alle Werte ignorieren:
[,,] = f();
Herausziehen von Werten aus Übereinsimmungen von regulären Ausdrücken
Wenn die Regex-Methode exec()
eine Übereinstimmung findet, gibt sie ein Array zurück, welches als erstes den kompletten übereinstimmenden Teil des Strings enthält und danach die Teile, welche mit den geklammerten Gruppierungen übereinstimmen. Mit Hilfe von Destructuring Assignment lassen sich letztere Teile sehr einfach extrahieren, während der erste Teil (die volle Übereinstimmung) ignoriert wird.
// Simple regular expression to match http / https / ftp-style URLs. var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url); if (!parsedURL) return null; var [, protocol, fullhost, fullpath] = parsedURL;u