he sc e ut ab De usg A
Erkunden Sie die Stärken von JavaScript
Das Beste an
JavaScript Douglas Crockford
O’Reilly
Deutsche Übersetzung von Peter Klicman
Das Beste an JavaScript
Douglas Crockford
Deutsche Übersetzung von Peter Klicman
Beijing · Cambridge · Farnham · Köln · Sebastopol · Taipei · Tokyo
Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen. Kommentare und Fragen können Sie gerne an uns richten: O'Reilly Verlag Balthasars«. 81 50670 Köln Tel.: 0221/9731600 Fax: 0221/9731608 E-Mail:
[email protected] Copyright der deutschen Ausgabe: © 2008 by O'Reilly Verlag GmbH &r Co. KG 1. Auflage 2008 Die Originalausgabe erschien 2008 unter dem Titel JavaScript - The Good Parts bei O'Reilly Media, Inc. Die Darstellung eines Afrikanischen Monarchen im Zusammenhang mit dem Thema JavaScript ist ein Warenzeichen von O'Reilly Media, Inc.
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar. Übersetzung und deutsche Bearbeitung: Peter Klicman, Köln Lektorat: Inken Kiupel, Köln Fachliche Unterstützung: Stefan Fröhlich, Berlin Korrektorat: Friederike Daenecke, Zülpich Satz: FKM, Neumünster Umschlaggestaltung: Edie Freedman & Hanna Dyer, Boston Produktion: Astrid Sander, Köln Belichtung, Druck und buchbinderische Verarbeitung: Druckerei Kösel, Krugzell; www.koeselbuch.de ISBN 978-3-89721-876-5 Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.
First
Inhalt
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XI 1
Gute Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Warum JavaScript? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Analyse von JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Das Versuchsgelände . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2
Grammatik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3
Max. Linie
Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Objektliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Abruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Prototyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Reflexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Aufzählung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Löschung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Reduzierung globaler Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
V
Max. Linie
Links 4
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Funktionsobjekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Funktionsliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Aufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Argumente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Typen erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Geltungsbereich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Closure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Kaskaden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Curry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Memoization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5
Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Pseudoklassische Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Objekt-Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Prototypische Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Funktionale Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Teile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Array-Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Aufzählung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Verwirrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Dimensionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7
Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Konstruktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Max. Linie
Max. Linie VI
|
Inhalt
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts 8
Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9
Stil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
10 Schöne Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 A
Furchtbare Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
B
Schlechte Seiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
C
JSLint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
D
Syntaxdiagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
E
JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Inhalt |
VII
Für die Jungs: Clement, Philbert, Seymore, Stern und – damit wir niemals vergessen – C. Twildo.
First
Vorwort
If we offend, it is with our good will That you should think, we come not to offend, But with good will. To show our simple skill, That is the true beginning of our end. – William Shakespeare, A Midsummer Night’s Dream
Dieses Buch behandelt die Programmiersprache JavaScript. Es richtet sich an Programmierer, die sich, durch Zufall oder Neugier, zum ersten Mal an JavaScript heranwagen. Es richtet sich auch an Programmierer, die bereits erste Erfahrungen mit JavaScript gesammelt haben und sich nun auf eine tiefergehende Beziehung mit der Sprache einlassen wollen. JavaScript ist eine überraschend leistungsfähige Sprache. Ihre unkonventionelle Art stellt einen vor Herausforderungen, aber da es eine kleine Sprache ist, lassen sich diese recht einfach meistern. Mein Ziel besteht darin, Ihnen dabei zu helfen, in JavaScript zu denken. Ich zeige Ihnen die Komponenten der Sprache und bereite Sie darauf vor zu entdecken, wie diese Komponenten zusammengefügt werden können. Dieses Buch ist kein Referenzwerk. Es geht nicht umfassend auf die Sprache und ihre Elemente ein. Es geht auch nicht auf alles ein, was Sie irgendwann einmal brauchen könnten. Diese Dinge sind online sehr einfach zu finden. Stattdessen enthält dieses Buch nur die Dinge, die wirklich wichtig sind. Dies ist kein Buch für Anfänger. Ich hoffe, eines Tages ein Buch mit dem Titel JavaScript – Die ersten Schritte zu schreiben, aber das ist nicht das Buch, das Sie in den Händen halten. Dies ist kein Buch über Ajax oder Webprogrammierung. Es konzentriert sich ausschließlich auf JavaScript, das nur eine von vielen Sprachen ist, die ein Webentwickler meistern muss.
Max. Linie
Dies ist kein Buch für Dummies. Es ist klein, aber sehr kompakt, die Informationsdichte ist hoch. Lassen Sie sich nicht entmutigen, wenn Sie es öfter lesen müssen, um es zu verstehen. Ihre Bemühungen werden belohnt werden.
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
XI
Max. Linie
Links Verwendete Konventionen In diesem Buch werden die folgenden typografischen Konventionen verwendet: Kursiv Wird für neue Begriffe, URLs, Dateinamen und Dateierweiterungen verwendet. Nichtproportionalschrift
Wird für Computer-Code im weitesten Sinne verwendet. Das umfasst Befehle, Optionen, Variablen, Attribute, Schlüssel, Requests, Funktionen, Methoden, Typen, Klassen, Module, Eigenschaften, Parameter, Werte, Objekte, Events, Event-Handler, XML- und XHTML-Tags, Makros und Schlüsselwörter. Nichtproportionalschrift fett
Wird für Befehle oder anderen Text verwendet, der vom Benutzer literal einzugeben ist.
Verwendung der Codebeispiele Dieses Buch soll Ihnen bei der Arbeit helfen. Den Code, den wir hier zeigen, dürfen Sie generell in Ihren Programmen und Dokumentationen verwenden. Sie brauchen uns nicht um Genehmigung zu bitten, sofern Sie nicht große Teile des Codes reproduzieren. Wenn Sie zum Beispiel ein Programm schreiben, das mehrere Codeabschnitte aus diesem Buch wiederverwendet, brauchen Sie unser Einverständnis nicht. Doch wenn Sie eine CD-ROM mit Code-Beispielen aus O’Reilly-Büchern verkaufen oder verteilen wollen, müssen Sie sehr wohl eine Erlaubnis einholen. Eine Frage mit einem Zitat aus diesem Buch und seinen Codebeispielen zu beantworten erfordert keine Erlaubnis, aber es ist nicht ohne Weiteres gestattet, große Teile unseres Texts oder Codes in eine eigene Produktdokumentation aufzunehmen. Wir freuen uns über eine Quellenangabe, verlangen sie aber nicht unbedingt. Zu einer Quellenangabe gehören normalerweise der Titel, der Autor, der Verlag und die ISBN, zum Beispiel »Douglas Crockford: Das Beste an JavaScript, 1. Auflage, O’Reilly Verlag 2008, ISBN 978-3-89721-876-5«. Wenn Sie das Gefühl haben, dass Ihr Einsatz unserer Codebeispiele über die Grenzen des Erlaubten hinausgeht, schreiben Sie uns bitte eine E-Mail an permissions@ oreilly.com.
Danksagungen Max. Linie
Ich möchte den Fachgutachtern danken, die mich auf meine vielen Fehler hingewiesen haben. Nur wenige Dinge im Leben sind besser als wirklich schlaue Menschen, die einen auf Patzer hinweisen. Es ist umso besser, wenn sie das tun, bevor man an
XII
|
Vorwort
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts die Öffentlichkeit geht. Vielen Dank Steve Souders, Bill Scott, Julien Lecomte, Stoyan Stefanov, Eric Miraglia und Elliotte Rusty Harold. Ich möchte den Leuten danken, mit denen ich bei Electric Communities und State Software zusammengearbeitet habe und die mir dabei halfen zu erkennen, dass es tief im Inneren von JavaScript Gutes gibt. Insbesondere möchte ich Chip Morningstar, Randy Farmer, John La, Mark Miller, Scott Shattuck und Bill Edney danken. Ich möchte Yahoo! Inc. dafür danken, dass sie mir die Zeit gaben, an diesem Projekt zu arbeiten, und dafür, dass Jahoo ein so großartiger Arbeitgeber ist, und ich möchten allen (ehemaligen und gegenwärtigen) Mitgliedern der Ajax Strike Force danken. Ich möchte auch O’Reilly Media, Inc., insbesondere Mary Treseler, Simon St. Laurent und Sumita Mukherji dafür danken, dass die Dinge so glatt liefen. Mein besonderer Dank gilt Professor Lisa Drake für all die Dinge, die sie tut. Und ich möchte den Leuten im ECMA TC39 danken, die darum ringen, ECMAScript zu einer besseren Sprache zu machen. Schließlich danke ich Brendan Eich, dem am meisten missverstandenen Programmiersprachen-Designer der Welt, ohne den dieses Buch nicht notwendig gewesen wäre.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Vorwort
|
XIII
First
Kapitel 1
KAPITEL 1
Gute Seiten
…setting the attractions of my good parts aside I have no other charms. – William Shakespeare, The Merry Wives of Windsor
Als ich noch ein junger Programmierer war, lernte ich jedes Feature der von mir genutzten Sprachen, und ich versuchte, bei der Entwicklung auch all diese Features zu nutzen. Es handelte sich dabei wohl um eine Art Prahlerei, und ich glaube, dass sie funktionierte, weil ich derjenige war, zu dem die Leute kamen, wenn sie etwas über ein bestimmtes Feature wissen wollten. Schließlich wurde mir klar, dass einige dieser Features mehr Ärger verursachten, als sie wert waren. Einige waren schlecht spezifiziert und führten so eher zu Portabilitätsproblemen. Einige führten zu Code, der nur schwer zu lesen oder zu modifizieren war. Einige brachten mich dazu, in einer Art und Weise zu entwickeln, die zu kompliziert und fehleranfällig war. Und einige dieser Features waren einfach Designfehler. Manchmal machen auch Sprachdesigner Fehler. Die meisten Programmiersprachen besitzen gute und schlechte Seiten. Ich habe irgendwann erkannt, dass ich ein besserer Programmierer wäre, wenn ich nur die guten Seiten nutzen und die schlechten meiden würde. Wie soll man auch etwas Gutes aus schlechten Komponenten aufbauen? Es ist einem Standardisierungsausschuss nur selten möglich, Mängel aus einer Sprache zu entfernen, weil das zu einem Bruch in all den schlechten Programmen führen würde, die von diesen schlechten Seiten der Sprache abhängig sind. Üblicherweise ist so ein Ausschuss machtlos und kann nicht mehr tun, als weitere Features über die vorhandenen Mängel zu stülpen. Und diese neuen Features fügen sich nicht immer harmonisch ein und führen so zu weiteren schlechten Seiten.
Max. Linie
Aber Sie haben die Möglichkeit, eine eigene Teilmenge zu definieren. Sie können bessere Programme schreiben, indem Sie sich ausschließlich auf das Beste an JavaScript beschränken.
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
1
Max. Linie
Links JavaScript ist eine Sprache, die mehr als genug schlechte Seiten besitzt. Sie kam aus dem Nichts und breitete sich in alarmierend kurzer Zeit weltweit aus. Es gab nie eine Laborphase, in der sie ausprobiert und aufpoliert werden konnte. Sie wurde, so wie sie war, direkt in Netscape Navigator 2 integriert, und war zu diesem Zeitpunkt nicht sonderlich ausgereift. Als Java™-Applets versagten, wurde JavaScript zur »Sprache des Web«. Die Popularität von JavaScript hat nahezu nichts mit seiner Qualität als Programmiersprache zu tun. Glücklicherweise besitzt JavaScript einige außerordentlich gute Seiten. Hinter JavaScript verbirgt sich eine schöne, elegante, sehr ausdrucksstarke Sprache, die sich unter einem Stapel guter Absichten und grober Schnitzer versteckt. Das Gute an JavaScript ist so gut versteckt, dass viele Jahre die Meinung vorherrschte, JavaScript sei ein unansehnliches, inkompetentes Spielzeug. Meine Absicht besteht darin, das Gute an JavaScript, einer hervorragenden dynamischen Programmiersprache, aufzuzeigen. JavaScript ist wie ein Marmorblock, und ich schlage die nicht so schönen Teile weg, bis sich die wahre Natur der Sprache offenbart. Ich glaube, dass die von mir herausgearbeitete elegante Teilmenge der Sprache an sich insgesamt deutlich überlegen ist und sie auf diese Weise zuverlässiger, lesbarer und wartbarer macht. Dieses Buch versucht nicht, die Sprache vollständig zu beschreiben. Stattdessen konzentriere ich mich auf die besten Seiten mit gelegentlichen Warnungen zur Vermeidung der schlechten. Die hier beschriebene Teilmenge kann verwendet werden, um zuverlässige und lesbare Programme zu entwickeln, egal ob klein oder groß. Indem wir uns nur auf die guten Teile konzentrieren, können wir die Lernzeit reduzieren, die Robustheit erhöhen und einige Bäume retten. Der vielleicht größte Vorteil daran, sich mit dem Besten an JavaScript zu beschäftigen, besteht wohl darin, dass man sich die schlechten Seiten nicht abgewöhnen muss. Schlechte Angewohnheiten abzulegen ist sehr schwer. Das ist eine schmerzhafte Angelegenheit, der die meisten von uns mit größtem Widerwillen begegnen. Manche Sprachen besitzen Teilmengen, die Studenten einen besseren Einstieg ermöglichen. In diesem Fall bilde ich aber eine Teilmenge von JavaScript, die für den Profi besser funktioniert.
Warum JavaScript?
Max. Linie
JavaScript ist eine wichtige Sprache, weil sie die Sprache des Webbrowsers ist. Diese Verknüpfung mit dem Browser macht sie zu einer der populärsten Sprachen der Welt. Gleichzeitig ist sie eine der am meisten verachteten Programmiersprachen der Welt. Die API des Browsers, das Document Object Model (DOM) ist ziemlich furchtbar, und das wird unfairerweise JavaScript zum Vorwurf gemacht. Die Arbeit mit dem DOM wäre in jeder Sprache schmerzhaft. Das DOM ist schlecht spezifiziert und widersprüchlich implementiert. Dieses Buch geht nur am Rande auf das
2
|
Kapitel 1: Gute Seiten
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts DOM ein. Ich glaube, dass ein Buch über das Beste am DOM eine echte Herausforderung wäre. JavaScript wird wohl hauptsächlich deshalb verschmäht, weil es keine »andere« Sprache ist. Wenn Sie eine andere Sprache gut kennen und in einer Umgebung programmieren müssen, die nur JavaScript unterstützt, dann sind Sie gezwungen, JavaScript zu nutzen, und das ist lästig. Die meisten Leute machen sich in dieser Situation nicht einmal die Mühe, JavaScript zu erlernen, und sind dann überrascht, dass JavaScript deutliche Unterschiede zu den Sprachen aufweist, die sie normalerweise verwenden, und dass diese Unterschiede tatsächlich von Bedeutung sind. Das Überraschende an JavaScript ist, dass man Aufgaben erledigen kann, ohne viel über die Sprache oder gar über Programmierung zu wissen. Es handelt sich um eine extrem ausdrucksstarke Sprache. Sie ist aber noch besser, wenn Sie wissen, was Sie tun. Die Programmierung ist ein schwieriges Geschäft und sollte deshalb nie mit Ignoranz erfolgen.
Analyse von JavaScript JavaScript baut auf einigen sehr guten und einigen sehr schlechten Ideen auf. Zu den sehr guten Ideen gehören Funktionen, die schwache Typisierung von Datentypen, dynamische Objekte und eine ausdrucksstarke literale Objektnotation. Zu den schlechten Ideen gehört ein auf globalen Variablen basierendes Programmiermodell. Die Funktionen in JavaScript sind Objekte erster Klasse mit (nahezu) lexikalischem Geltungsbereich. JavaScript ist die erste Lambda-Sprache, die wirklich populär geworden ist. Tief im Inneren hat JavaScript mehr mit Lisp und Scheme gemeinsam, als mit Java. Es ist ein Lisp im C-Gewand. Das macht JavaScript zu einer bemerkenswert leistungsfähigen Sprache.
Max. Linie
Heutzutage ist bei den meisten Programmiersprachen die sogenannte strenge Typisierung in Mode. Die Theorie besagt, dass die strenge Typisierung dem Compiler ermöglicht, eine Vielzahl von Fehlern schon während der Kompilierung zu erkennen. Je früher wir Fehler erkennen und korrigieren können, desto weniger kosten sie uns. Das kann für diejenigen Leute ein alarmierendes Zeichen sein, die von streng typisierten Sprachen zu JavaScript kommen. Es zeigt sich aber, dass die strenge Typisierung die Notwendigkeit sorgfältigen Testens nicht ersetzt. Und ich habe während meiner Arbeit festgestellt, dass die Art von Fehlern, die durch die strenge Typisierung aufgedeckt werden, nicht die Fehler sind, um die ich mir Sorgen mache. Andererseits empfinde ich die schwache Typisierung als befreiend. Ich muss keine komplexen Klassenhierarchien aufbauen. Und ich muss niemals ein Typecasting vornehmen oder mich mit dem Typsystem herumschlagen, um das von mir gewünschte Verhalten zu erreichen.
Analyse von JavaScript This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
3
Max. Linie
Links JavaScript besitzt eine sehr mächtige literale Objektnotation. Objekte können erzeugt werden, indem man einfach ihre Komponenten auflistet. Diese Notation war die Inspiration für JSON, das populäre Datenaustauschformat. (Mehr zu JSON finden Sie in Anhang E.) Ein kontroverses JavaScript-Feature ist die prototypische Vererbung. JavaScript besitzt ein klassenfreies Objektsystem, bei dem Objekte Eigenschaften direkt von anderen Objekten erben. Das ist ein wirklich mächtiges Feature, den klassenorientiert geschulten Programmierern aber nicht vertraut. Wenn Sie versuchen, klassische Entwurfsmuster direkt auf JavaScript anzuwenden, werden Sie schnell frustriert sein. Wenn Sie aber lernen, mit der prototypischen Natur von JavaScript umzugehen, werden Ihre Bemühungen belohnt. JavaScript wird für die Wahl seiner Schlüsselideen sehr schlechtgemacht. Allerdings war diese Wahl in den meisten Fällen gut, wenn auch ungewöhnlich. Eine Entscheidung war aber besonders schlecht: JavaScript verwendet globale Variablen für die Verlinkung. Alle Top-Level-Variablen aller Kompilierungseinheiten werden in einem gemeinsamen Namensraum zusammengefasst, der als globales Objekt (global object) bezeichnet wird. Das ist schlecht, weil globale Variablen bösartig sind, und bei JavaScript bilden sie ein fundamentales Element. Glücklicherweise gibt uns JavaScript (wie Sie noch sehen werden) Mittel und Wege an die Hand, um dieses Problem zu umgehen. In einigen wenigen Fällen können wir die schlechten Seiten nicht ignorieren. Es gibt einige unvermeidbare furchtbare Seiten, auf die wir eingehen, wenn wir auf sie treffen. Sie werden außerdem in Anhang A zusammengefasst. Einen Großteil der schlechten Seiten können wir in diesem Buch aber umgehen, und was wir ausgelassen haben, fassen wir in Anhang B zusammen. Wenn Sie mehr über die schlechten Seiten erfahren wollen und darüber, wie man sie nicht gut einsetzt, können sie jedes andere JavaScript-Buch zurate ziehen. Der Standard, der JavaScript (alias JScript) definiert, ist die dritte Ausgabe von The ECMAScript Programming Language, die über http://www.ecma-international. org/publications/files/ecma-st/ECMA-262.pdf verfügbar ist. Die in diesem Buch beschriebene Sprache ist eine saubere Untermenge von ECMAScript. Dieses Buch beschreibt nicht die gesamte Sprache, da es ja die schlechten Seiten ausspart. Unsere Betrachtung ist nicht umfassend, da sie Grenzfälle auslässt. Sie sollten das auch tun. An den Grenzen lauern Gefahr und Elend.
Max. Linie
Anhang C beschreibt ein Programmiertool namens JSLint, einen JavaScript-Parser, der ein JavaScript-Programm analysieren und die darin enthaltenen schlechten Seiten aufdecken kann. JSLint bietet ein Maß an Strenge, das in der JavaScript-Entwicklung generell fehlt. Es gibt Ihnen die Sicherheit, dass Ihre Programme nur die guten Seiten enthalten.
4
|
Kapitel 1: Gute Seiten
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts JavaScript ist eine Sprache vieler Gegensätze. Sie enthält viele Fehler und scharfe Kanten, weshalb Sie sich fragen könnten, warum Sie überhaupt JavaScript verwenden sollten. Darauf gibt es zwei Antworten. Die erste lautet, dass Sie keine andere Wahl haben. Das Web ist zu einer wichtigen Plattform für die Anwendungsentwicklung geworden, und JavaScript ist die einzige Sprache, die bei allen Browsern zu finden ist. Unglücklicherweise hat sich Java in dieser Umgebung nicht durchgesetzt. Wäre das der Fall, hätten all diejenigen, die sich eine streng typisierte, klassenorientierte Sprache wünschen, eine Alternative zu JavaScript. Aber Java hat sich nicht durchgesetzt, und JavaScript floriert, was man als Beweis dafür nehmen könnte, dass JavaScript etwas richtig gemacht hat. Die andere Antwort lautet, dass, trotz aller Defizite, JavaScript wirklich gut ist. Die Sprache ist leichtgewichtig und ausdrucksstark. Und sobald man den Bogen einmal raus hat, macht funktionale Programmierung wirklich Spaß. Um die Sprache aber vernünftig einsetzen zu können, müssen Sie über ihre Beschränkungen genau informiert sein. Auf diese werde ich mit einer gewissen Brutalität eingehen. Lassen Sie sich davon nicht entmutigen. Das Beste an JavaScript ist gut genug, um die schlechten Seiten auszugleichen.
Das Versuchsgelände Wenn Sie einen Webbrowser und einen Texteditor besitzen, haben Sie alles, was man braucht, um JavaScript-Programme ausführen. Zuerst legen Sie eine HTMLDatei mit einem Namen wie programm.html an: <pre><script src="programm.js">
Dann legen Sie im gleichen Verzeichnis eine Datei mit einem Namen wie programm.js an: document.writeln('Hallo, Welt!');
Als Nächstes öffnen Sie die HTML-Datei in Ihrem Webbrowser und sehen sich das Ergebnis an. Während des gesamten Buches wird eine method-Methode zur Definition neuer Methoden verwendet. Hier ist ihre Definition: Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; };
Sie wird in Kapitel 4 erläutert.
Max. Linie
Max. Linie Das Versuchsgelände This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
5
FirstLeft. Kapitel 2 2 KAPITEL
Grammatik
I know it well: I read it in the grammar long ago. – William Shakespeare, The Tragedy of Titus Andronicus
Dieses Kapitel stellt die Grammatik der guten Seiten von JavaScript vor und gibt Ihnen eine kurze Übersicht zur Struktur der Sprache. Wir stellen die Grammatik mit Syntaxdiagrammen dar. Die Regeln zur Interpretation dieser Diagramme ist einfach: • Sie beginnen am linken Rand und folgen der Linie bis zum rechten Rand. • Auf Ihrem Weg treffen Sie auf Literale in Ovalen und auf Regeln oder Beschreibungen in Rechtecken. • Jede Sequenz, die möglich ist, indem man den Linien folgt, ist gültig. • Jede Sequenz, die nicht möglich ist, indem man den Linien folgt, ist ungültig. • Syntaxdiagramme mit einem Balken an jedem Ende erlauben das Einfügen von Whitespace zwischen jedem Token-Paar. Bei Syntaxdiagrammen mit zwei Balken an jedem Ende ist das hingegen nicht erlaubt. Die Grammatik der in diesem Kapitel vorgestellten guten Teile ist deutlich einfacher als die Grammatik der gesamten Sprache.
Whitespace Whitespace kann die Form von Formatierungszeichen oder Kommentaren annehmen. Whitespace-Zeichen sind üblicherweise bedeutungslos, gelegentlich aber notwendig, um Zeichenfolgen voneinander zu trennen, die anderenfalls zu einem einzigen Token zusammengefasst werden würden. Zum Beispiel kann bei
Max. Linie
var that = this;
das Leerzeichen zwischen var und that nicht entfernt werden, die anderen Leerzeichen aber schon.
6
|
Kapitel 2: Grammatik
Max. Linie
Rechts
JavaScript kennt zwei Formen von Kommentaren: Block-Kommentare, die mit /* */ gebildet werden, und bis zum Zeilenende laufende Kommentare, die mit // beginnen. Kommentare sollten sehr freizügig verwendet werden, um die Lesbarkeit Ihrer Programme zu erhöhen. Achten Sie darauf, dass die Kommentare immer genau den Code beschreiben. Veraltete Kommentare sind schlimmer als gar keine. Die /* */-Form des Block-Kommentars stammt aus einer Sprache namens PL/I. PL/I wählte diese seltsamen Zeichenpaare als Symbole für Kommentare, weil es sehr unwahrscheinlich war, dass sie in Programmen dieser Sprache irgendwo auftreten würden (außer vielleicht in String-Literalen). Bei JavaScript können diese Paare auch in Regex-Literalen vorkommen, so dass Block-Kommentare nicht sicher sind, wenn man ganze Codeblöcke auskommentieren möchte. Dieses Beispiel: /* var rm_a = /a*/.match(s); */
führt zu einem Syntaxfehler. Es wird daher empfohlen, /* */-Kommentare zu vermeiden und stattdessen //-Kommentare zu verwenden. In diesem Buch verwenden wir ausschließlich //-Kommentare.
Namen Max. Linie
Ein Name ist ein Buchstabe, auf den optional ein oder mehrere Buchstaben, Ziffern oder Unterstriche folgen. Ein Name darf keines der folgenden reservierten Wörter sein:
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Namen
|
7
Max. Linie
Links abstract boolean break byte case catch char class const continue debugger default delete do double else enum export extends false final finally float for function goto if implements import in instanceof int interface long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var volatile void while with
Die meisten reservierten Wörter dieser Liste werden in der Sprache nicht verwendet. Die Liste enthält einige Wörter nicht, die man hätte reservieren sollen, wie etwa undefined, NaN und Infinity. Es ist nicht erlaubt, eine Variable oder einen Parameter mit einem reservierten Wort zu benennen. Noch schlimmer ist, dass man ein reserviertes Wort auch nicht als Name einer Objekt-Eigenschaft in einem Objektliteral oder hinter einem Punkt verwenden darf. Namen werden für Anweisungen, Variablen, Parameter, Eigenschaftsnamen, Operatoren oder Label verwendet.
Zahlen
JavaScript besitzt nur einen einzigen Zahlentyp. Intern wird dieser als 64-Bit-Fließkommazahl repräsentiert, der dem Java-Typ double entspricht. Im Gegensatz zu
Max. Linie
Max. Linie 8
|
Kapitel 2: Grammatik
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts den meisten anderen Programmiersprachen gibt es keinen separaten Integertyp, d.h., 1 und 1.0 sind der gleiche Wert. Das ist eine deutliche Erleichterung, weil Überlaufprobleme kleiner Integerwerte vollständig vermieden werden und alles, was Sie über eine Zahl wissen müssen, ist, dass sie eine Zahl ist. Eine große Klasse numerischer Typfehler wird so vermieden.
Besitzt ein Zahlenliteral einen Exponenten, berechnet sich der Wert des Literals durch die Multiplikation des Teils vor dem e mal 10 hoch dem Teil hinter dem e. Das bedeutet also, dass 100 und 1e2 die gleiche Zahl darstellen. Negative Zahlen können mithilfe der Präfixnotation (–) gebildet werden. Der Wert NaN ist das Ergebnis einer Operation, die kein normales Ergebnis produzieren konnte. NaN ist ungleich jedem anderen Wert (einschließlich sich selbst). Sie können NaN mit der Funktion isNaN(zahl) ermitteln. Der Wert Infinity (Unendlich) repräsentiert alle Werte, die größer sind als 1.79769313486231570e+308. Zahlen besitzen Methoden (siehe Kapitel 8). JavaScript besitzt ein Math-Objekt, das eine Reihe von Methoden enthält, die mit Zahlen arbeiten. Zum Beispiel kann die Methode Math.floor(zahl) verwendet werden, um eine Zahl in einen Integerwert umzuwandeln.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Zahlen
|
9
Links Strings
Ein String-Literal kann zwischen einfachen oder doppelten Anführungszeichen stehen. Es kann null oder mehr Zeichen enthalten. Das \ (Backslash) ist das EscapeZeichen. JavaScript wurde zu einer Zeit entwickelt, als Unicode ein 16-Bit-Zeichensatz war, weshalb alle Zeichen bei JavaScript 16 Bit breit sind.
Max. Linie
JavaScript besitzt keinen Zeichentyp. Wollen Sie ein einzelnes Zeichen repräsentieren, verwenden Sie einen String, der nur ein Zeichen enthält.
10
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Die Escape-Sequenzen ermöglichen das Einbinden von Zeichen, die in Strings normalerweise nicht erlaubt sind, beispielsweise Backslashes, Anführungs- und Steuerzeichen. Die \u-Konvention erlaubt es, Zeichen numerisch anzugeben. "A" === "\u0041"
Strings besitzen eine length-Eigenschaft. So ist "sieben".length gleich 6. Strings sind unveränderlich. Sobald ein String einmal angelegt wurde, kann er nicht mehr geändert werden. Man kann aber auf einfache Weise einen neuen String erzeugen, indem man andere Strings über den Operator + miteinander verkettet. Zwei Strings, die genau die gleichen Zeichen in genau der gleichen Reihenfolge enthalten, werden als gleich betrachtet. Daher ist 'c' + 'a' + 't' === 'cat'
also wahr. Strings besitzen Methoden (siehe Kapitel 8): 'cat'.toUpperCase( ) === 'CAT'
Anweisungen
Eine Kompilierungseinheit (compilation unit) enthält eine Reihe ausführbarer Anweisungen. In Webbrowsern bildet jedes <script>-Tag eine Kompilierungseinheit, die kompiliert und unmittelbar ausgeführt wird. Da ein Linker fehlt, packt JavaScript sie alle in einem gemeinsamen globalen Namensraum zusammen. Mehr zu globalen Variablen finden Sie in Anhang A. Wird sie innerhalb einer Funktion verwendet, definiert die var-Anweisung die privaten Variablen der Funktion. Die Anweisungen switch, while, for und do dürfen ein optionales label-Präfix besitzen, das mit der break-Anweisung zusammenwirkt.
Max. Linie
Max. Linie Anweisungen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
11
Links
Anweisungen werden in der Regel von oben nach unten ausgeführt. Die Ausführungssequenz kann durch die Bedingungsanweisungen (if und switch), durch Schleifenanweisungen (while, for und do), durch unterbrechende Anweisungen (break, return und throw) sowie durch Funktionsaufrufe verändert werden.
Max. Linie
Ein Block ist eine in geschweiften Klammern eingeschlossene Gruppe von Anweisungen. Im Gegensatz zu vielen anderen Sprachen erzeugen Blöcke in JavaScript keinen neuen Geltungsbereich, d.h., Variablen müssen zu Beginn der Funktion und nicht in Blöcken definiert werden.
12
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts
Die if-Anweisung ändert den Programmfluss basierend auf dem Wert des Ausdrucks. Der then-Block wird ausgeführt, wenn der Ausdruck wahr ist. Anderenfalls wird der optionale else-Zweig ausgeführt. Hier die falsch-Werte: • false • null • undefined
• der leere String '' • die Zahl 0 • die Zahl NaN Alle anderen Werte sind wahr, einschließlich true, des Strings 'false' und aller Objekte.
Die switch-Anweisung führt eine Mehrfachverzweigung durch. Sie vergleicht den Ausdruck auf Gleichheit mit allen angegebenen Fällen (engl. case). Der Ausdruck kann eine Zahl oder einen String produzieren. Wird ein exakter Treffer erkannt, werden die Anweisungen der entsprechenden case-Klausel ausgeführt. Gibt es keinen Treffer, werden die optionalen Default-Anweisungen ausgeführt.
Max. Linie
Eine case-Klausel enthält einen oder mehrere case-Ausdrücke. Die case-Ausdrücke müssen keine Konstanten sein. Die auf eine Klausel folgende Anweisung sollte eine
Anweisungen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
13
Max. Linie
Links Unterbrechungsanweisung sein, um ein Durchrutschen zum nächsten case zu verhindern. Die break-Anweisung kann verwendet werden, um aus einer switch-Anweisung auszusteigen.
Die while-Anweisung führt eine einfache Schleife durch. Ist der Ausdruck falsch, wird die Schleife beendet. Solange der Ausdruck wahr ist, wird der Block ausgeführt. Die for-Anweisung ist eine etwas kompliziertere Schleifenanweisung. Sie besitzt zwei Formen.
Die konventionelle Form wird durch drei optionale Klauseln gesteuert: die Initialisierung, die Bedingung und das Inkrement. Zuerst erfolgt die Initialisierung, was üblicherweise die Initialisierung der Schleifenvariablen bedeutet. Dann wird die Bedingung evaluiert. Typischerweise wird die Schleifenvariable hier mit einem Abbruchkriterium verglichen. Lässt man die Bedingung weg, wird die Bedingung mit true angenommen. Ist die Bedingung falsch, wird die Schleife beendet. Anderenfalls wird der Block ausgeführt, dann wird das Inkrement ausgeführt und anschließend die Schleife mit der Bedingung erneut durchlaufen. Die andere Form (for...in genannt) geht die Eigenschaften-Namen (oder Schlüssel) eines Objekts durch. Bei jeder Iteration wird ein anderer Eigenschaftsname aus dem Objekt an die Variable zugewiesen.
Max. Linie
Max. Linie 14
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Es ist üblicherweise nötig, auf object.hasOwnProperty(variable) zu testen, um zu ermitteln, ob der Eigenschaften-Name wirklich Teil des Objekts ist oder in der Prototyp-Kette gefunden wurde. for (myvar in obj) { if (obj.hasOwnProperty(myvar)) { ... } }
Die do-Anweisung ähnelt der while-Anweisung, nur dass der Ausdruck überprüft wird, nachdem der Block ausgeführt wurde (und nicht vorher). Das bedeutet, dass der Block immer mindestens einmal ausgeführt wird.
Die try-Anweisung führt einen Block aus und fängt alle Ausnahmen ab, die durch den Block ausgelöst wurden. Die catch-Klausel definiert eine neue variable, die das Ausnahmeobjekt aufnimmt.
Die throw-Anweisung löst eine Ausnahme aus. Liegt die throw-Anweisung in einem try-Block, wird die Kontrolle an die catch-Klausel übergeben. Anderenfalls wird der Funktionsaufruf abgebrochen, und die Kontrolle geht an die catch-Klausel der try-Anweisung der aufrufenden Funktion über. Der Ausdruck ist üblicherweise ein Objektliteral mit einer name- und einer messageEigenschaft. Die Funktion, die die Ausnahme abfängt, kann diese Information nutzen, um zu ermitteln, was zu tun ist.
Max. Linie
Max. Linie Anweisungen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
15
Links Die return-Anweisung ermöglicht eine Rückkehr aus einer Funktion. Sie kann auch den zurückzugebenden Wert festlegen. Ist kein return-Ausdruck vorhanden, wird undefined zurückgegeben. JavaScript erlaubt kein Zeilenende zwischen dem return und dem Ausdruck.
Die break-Anweisung beendet eine Schleifen- oder switch-Anweisung. Sie können optinal ein label angeben, was die entsprechend benannte Anweisung beendet. JavaScript erlaubt kein Zeilenende zwischen dem return und dem Ausdruck.
Eine ausdrucksanweisung kann Werte an eine oder mehrere Variablen bzw. Member zuweisen, eine Methode aufrufen oder eine Eigenschaft aus einem Objekt entfernen. Der Operator = wird für die Zuweisung verwendet. Verwechseln Sie ihn nicht mit dem Gleichheitsoperator ===. Der Operator += kann addieren oder verketten.
Ausdrücke
Max. Linie
Die einfachsten Ausdrücke sind ein literaler Wert (wie ein String oder eine Zahl), eine Variable, ein fest eingebauter Wert (true, false, null, undefined, NaN oder Infinity), ein Konstruktoraufruf mit vorangestelltem new, ein Spezifizierungsausdruck mit vorangestelltem delete, ein von Klammern umschlossener Ausdruck, ein Ausdruck, dem ein Präfix-Operator vorangestellt wurde, oder ein Ausdruck gefolgt von:
16
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts
• einem Infix-Operator und einem weiteren Ausdruck • dem ternären Operator ?, gefolgt von einem weiteren Ausdruck, dann einem : und dann einem weiteren Ausdruck • einem Aufruf • eine Spezifizierung Der ternäre Operator ? verlangt drei Operanden. Ist der erste Operand wahr, erzeugt er den Wert des zweiten Operanden. Ist der erste Operand falsch, wird der Wert des dritten Operanden produziert. Die zu Beginn stehenden Operatoren der Operator-Vorrangliste in Tabelle 2-1 haben höheren Vorrang. Sie binden am höchsten. Die Operatoren am Ende haben den niedrigsten Vorrang. Klammern können genutzt werden, um den normalen Vorrang zu verändern: 2 + 3 * 5 === 17 (2 + 3) * 5 === 25
Tabelle 2-1: Operator-Vorrang
Max. Linie
. [] ( )
Spezifizierung und Aufruf
delete new typeof + - !
Unäre Operatoren
* / %
Multiplikation, Division, Modulo
Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
17
Links Tabelle 2-1: Operator-Vorrang (Fortsetzung) + -
Addition/Verkettung, Subtraktion
>= <= > <
Ungleichheit
=== !==
Gleichheit
&&
Logisches UND
||
Logisches ODER
?:
Ternärer Operator
Die von typeof erzeugten Werte sind 'number', 'string', 'boolean', 'undefined', 'function' und 'object'. Ist der Operand ein Array oder null, lautet das Ergebnis 'object', was falsch ist. Mehr zu typeof finden Sie in Kapitel 6 und Anhang A. Ist der Operand von ! wahr, erzeugt er false. Anderenfalls erzeugt er true.
Der Operator + addiert oder verkettet. Wenn Sie addieren wollen, müssen beide Operanden Zahlen sein.
Max. Linie
Der Operator / kann einen Bruch zurückliefern, auch wenn beide Operanden ganze Zahlen sind.
18
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Der Operator && liefert den Wert des ersten Operanden zurück, wenn der erste Operand falsch ist. Anderenfalls liefert er den Wert des zweiten Operanden zurück. Der Operator || liefert den Wert des ersten Operanden zurück, wenn der erste Operand wahr ist. Anderenfalls liefert er den Wert des zweiten Operanden zurück.
Ein Aufruf veranlasst die Ausführung eines Funktionswertes. Der Aufruf-Operator besteht aus einem Paar Klammern, die auf den Funktionswert folgen. Die Klammern können Argumente enthalten, die an die Funktion übergeben werden. Wesentlich mehr über Funktionen erfahren Sie in Kapitel 4.
Ein Spezifizierungsaufruf wird verwendet, um eine Eigenschaft oder ein Element eines Objekts oder Arrays zu spezifizieren. Darauf gehen wir im nächsten Kapitel detailliert ein.
Literale Objektliterale stellen eine praktische Notation zur Spezifikation neuer Objekte dar. Die Namen der Eigenschaften können in Form von Namen oder als Strings angegeben werden. Die Namen werden als literale Namen betrachtet, nicht als Variablennamen, so dass die Namen der Eigenschaften des Objekts während der Kompilierung bekannt sein müssen. Die Werte der Eigenschaften sind Ausdrücke. Mehr zu Objektliteralen erfahren Sie im nächsten Kapitel. Array-Literale stellen eine praktische Notation zur Spezifikation neuer Arrays dar. Mehr über Array-Literale erfahren Sie in Kapitel 6. Mehr über reguläre Ausdrücke erfahren Sie in Kapitel 7.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Literale
|
19
Links
Funktionen
Max. Linie
Ein Funktionsliteral definiert einen Funktionswert. Es kann einen optionalen Namen besitzen, so dass es sich selbst rekursiv aufrufen kann. Es kann eine Liste von Parametern spezifizieren, die als Variablen dienen und durch die Aufrufargu-
20
|
Kapitel 2: Grammatik This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts
mente initialisiert werden. Der Rumpf (Body) der Funktion umfasst Variablendefinitionen und Anweisungen. Mehr über Funktionen erfahren Sie in Kapitel 4.
Max. Linie
Max. Linie Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
21
FirstLeft. Kapitel 3 3 KAPITEL
Objekte
Upon a homely object Love can wink. – William Shakespeare, The Two Gentlemen of Verona
Die einfachen Datentypen von JavaScript sind Zahlen, Strings, Boolesche Werte (true und false), null und undefined. Alle anderen Werte sind Objekte. Zahlen, Strings und Boolesche Werte ähneln ebenfalls Objekten (da sie Methoden besitzen), sind aber unveränderlich. Objekte sind bei JavaScript veränderbare, über Schlüssel gebundene Sammlungen. Bei JavaScript sind Arrays Objekte, Funktionen sind Objekte, reguläre Ausdrücke sind Objekte, und (natürlich) sind Objekte Objekte. Ein Objekt ist ein Container mit Eigenschaften, wobei eine Eigenschaft einen Namen und einen Wert besitzt. Der Name einer Eigenschaft kann ein beliebiger String sein, einschließlich des leeren Strings. Ein Eigenschaftswert kann jeder JavaScript-Wert außer undefined sein. Objekte sind in JavaScript klassenfrei. Es gibt keine Beschränkung im Bezug auf die Namen neuer Eigenschaften oder deren Wert. Objekte sind nützlich, um Daten zu sammeln und zu organisieren. Objekte können andere Objekte enthalten und können daher sehr leicht Baum- oder Graphen-Strukturen abbilden. JavaScript besitzt ein Feature zur Prototyp-Verknüpfung, mit dessen Hilfe ein Objekt die Eigenschaften eines anderen erben kann. Richtig eingesetzt, kann das die Objekt-Initialisierungszeit und den Speicherverbrauch reduzieren.
Objektliterale Max. Linie
Objektliterale stellen eine sehr praktische Notation zum Aufbau neuer Objekte dar. Ein Objektliteral besteht aus null oder mehr Schlüssel/Wert-Paaren, die von einem Paar geschweifter Klammern umschlossen sind. Ein Objektliteral kann überall da stehen, wo ein Ausdruck stehen kann:
22
|
Kapitel 3: Objekte
Max. Linie
Rechts var empty_object = {}; var stooge = { "first-name": "Jerome", "last-name": "Howard" };
Der Name einer Eigenschaft kann ein beliebiger String sein, einschließlich des LeerStrings. Die Anführungszeichen um den Eigenschaftsnamen in einem Objektliteral sind optional, wenn der Name ein gültiger JavaScript-Name und kein reserviertes Wort ist. Die Anführungszeichen sind also bei "first-name" notwendig, während sie bei first_name optional sind. Kommata werden verwendet, um die Paare zu trennen. Der Wert einer Eigenschaft kann durch einen beliebigen Ausdruck ermittelt werden, auch durch ein weiteres Objektliteral. Objekte können verschachtelt werden: var flight = { airline: "Oceanic", number: 815, departure: { IATA: "SYD", time: "2008-09-22 14:55", city: "Sydney" }, arrival: { IATA: "LAX", time: "2008-09-23 10:42", city: "Los Angeles" } };
Abruf Werte können aus einem Objekt abgerufen werden, indem man einen String-Ausdruck in ein [ ]-Suffix packt. Ist der String-Ausdruck eine Konstante, ein gültiger JavaScript-Name und kein reserviertes Wort, kann stattdessen die Punktnotation verwendet werden. Diese Punktnotation ist zu bevorzugen, da sie kompakter und besser zu lesen ist: stooge["first-name"] flight.departure.IATA
// "Joe" // "SYD"
Der Wert undefined wird zurückgegeben, wenn man versucht, ein nicht existierendes Member abzurufen:
Max. Linie
stooge["middle-name"] flight.status stooge["FIRST-NAME"]
// undefined // undefined // undefined
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Abruf
|
23
Links Der Operator || kann genutzt werden, um Standardwerte festzulegen: var middle = stooge["middle-name"] || "(none)"; var status = flight.status || "unknown";
Der Versuch, Werte von undefined abzurufen, löst eine TypeError-Ausnahme aus. Davor kann man sich mit dem Operator && schützen: flight.equipment flight.equipment.model flight.equipment && flight.equipment.model
// undefined // "TypeError" // undefined
Update Ein Wert kann innerhalb eines Objekts durch eine Zuweisung aktualisiert werden. Wenn der Eigenschaftsame in diesem Objekt bereits existiert, wird der Wert der Eigenschaft ersetzt: stooge['first-name'] = 'Jerome';
Kennt das Objekt den Eigenschaftsnamen noch nicht, wird das Objekt entsprechend erweitert: stooge['middle-name'] = 'Lester'; stooge.nickname = 'Curly'; flight.equipment = { model: 'Boeing 777' }; flight.status = 'overdue';
Referenz Objekte werden per Referenz übergeben. Sie werden niemals kopiert: var x = stooge; x.nickname = 'Curly'; var nick = stooge.nickname; // nick ist 'Curly', weil x und stooge // Referenzen auf das gleiche Objekt sind var a = {}, b = {}, c = {}; // a, b und c verweisen jeweils auf // ein anderes leeres Objekt a = b = c = {}; // a, b und c verweisen alle auf // das gleiche leere Objekt
Max. Linie
Max. Linie 24
|
Kapitel 3: Objekte
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Prototyp Jedes Objekt ist mit einem Prototyp-Objekt verknüpft, von dem es Eigenschaften erben kann. Alle mit Objektliteralen erzeugten Objekte sind mit Object.prototype verknüpft, einem standardmäßig zu JavaScript gehörenden Objekt. Wenn Sie ein neues Objekt anlegen, können Sie das Objekt auswählen, dessen Prototyp verwendet werden soll. Der zu diesem Zweck von JavaScript bereitgestellte Mechanismus ist umständlich und komplex, kann aber deutlich vereinfacht werden. Wir wollen die Object-Funktion um eine create-Methode erweitern. Die create-Methode erzeugt ein neues Objekt und verwendet dabei ein altes Objekts als Prototyp. Mit Funktionen befassen wir uns im nächsten Kapitel ausführlicher. if (typeof Object.create !== 'function') { Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); }; } var another_stooge = Object.create(stooge);
Der Prototyp-Link hat keine Auswirkung auf die Aktualisierung. Nehmen Sie Änderungen an einem Objekt vor, bleibt der Prototyp des Objekts unverändert: another_stooge['first-name'] = 'Harry'; another_stooge['middle-name'] = 'Moses'; another_stooge.nickname = 'Moe';
Der Prototyp-Link wird nur beim Abruf verwendet. Wenn wir versuchen, einen Eigenschaftswert aus einem Objekt abzurufen, und wenn diese Eigenschaft im Objekt nicht existiert, dann versucht JavaScript, den Eigenschaftswert aus dem Prototyp-Objekt abzurufen. Und fehlt auch in diesem Objekt diese Eigenschaft, geht es weiter mit dessen Prototyp und so weiter, bis der Prozess schließlich Object.prototype erreicht. Ist die gewünschte Eigenschaft innerhalb der Prototyp-Kette nicht zu finden, dann ist das Ergebnis der Wert undefined. Das wird als Delegation bezeichnet. Die Prototyp-Beziehung ist eine dynamische Beziehung. Wenn wir eine neue Eigenschaft in den Prototyp aufnehmen, wird diese Eigenschaft unmittelbar in allen Objekten sichtbar, die auf diesem Prototyp basieren: stooge.profession = 'actor'; another_stooge.profession // 'actor'
Mehr zur Prototyp-Kette erfahren sie in Kapitel 6.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Prototyp
|
25
Links Reflexion Es ist recht einfach, ein Objekt zu inspizieren, um zu ermitteln, welche Eigenschaften es besitzt, indem man versucht, die Eigenschaften abzurufen, und die darin enthaltenen Werte untersucht. Der typeof-Operator ist sehr hilfreich, wenn man den Typ einer Eigenschaft ermitteln will: typeof typeof typeof typeof
flight.number flight.status flight.arrival flight.manifest
// // // //
'number' 'string' 'object' 'undefined'
Eine gewisse Vorsicht ist notwendig, weil jede Eigenschaft der Prototyp-Kette einen Wert liefern kann: typeof flight.toString // 'function' typeof flight.constructor // 'function'
Es gibt zwei Ansätze, um mit diesen unerwünschten Eigenschaften umzugehen. Der erste besteht darin, Ihr Programm nach function-Werten suchen zu lassen und diese zu ignorieren. Generell sind Sie bei Reflexionen an Daten interessiert, und Sie sollten sich dessen bewusst sein, dass einige Werte Funktionen sein können. Der andere Ansatz besteht in der Nutzung der hasOwnProperty-Methode, die true zurückgibt, wenn das Objekt eine bestimmte Eigenschaft besitzt. Die Methode hasOwnProperty lässt die Prototyp-Kette außen vor: flight.hasOwnProperty('number') flight.hasOwnProperty('constructor')
// true // false
Aufzählung Die for...in-Anweisung bietet die Möglichkeit, eine Schleife über alle Eigenschaftsnamen in einem Objekt laufen zu lassen bzw. sie aufzuzählen. Die Aufzählung umfasst alle Eigenschaften – einschließlich Funktionen und Prototyp-Eigenschaften, an denen Sie möglicherweise nicht interessiert sind –, weshalb es notwendig ist, die unerwünschten Werte auszufiltern. Die gängigsten Filter sind die hasOwnProperty-Methode und die Verwendung von typeof zum Ausschluss von Funktionen: var name; for (name in another_stooge) { if (typeof another_stooge[name] !== 'function') { document.writeln(name + ': ' + another_stooge[name]); } }
Max. Linie
Es gibt keine Garantie in Bezug auf die Reihenfolge der Namen, d.h., Sie müssen darauf vorbereitet sein, dass diese in beliebiger Reihenfolge auftreten. Wenn Sie
26
|
Kapitel 3: Objekte
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts sicherstellen wollen, dass die Eigenschaften in einer bestimmten Reihenfolge erscheinen, verzichten Sie besser auf die for...in-Anweisung und erzeugen stattdessen ein Array, das die Namen der Eigenschaften in der gewünschten Reihenfolge enthält: var i; var properties = [ 'first-name', 'middle-name', 'last-name', 'profession' ]; for (i = 0; i < properties.length; i += 1) { document.writeln(properties[i] + ': ' + another_stooge[properties[i]]); }
Durch die Verwendung von for anstelle von for...in sind wir in der Lage, uns alle gewünschten Eigenschaften anzusehen, ohne uns darum kümmern zu müssen, was in der Prototyp-Kette los ist. Wir erhalten sie in der gewünschten Reihenfolge.
Löschung Der delete-Operator kann verwendet werden, um eine Eigenschaft aus einem Objekt zu enfernen. Er entfernt die Eigenschaft aus dem Objekt, wenn es diese Eigenschaft besitzt. Er fasst keines der Objekte in der Prototyp-Verknüpfung an. Das Löschen einer Eigenschaft aus einem Objekt lässt die Eigenschaft aus dem Prototyp-Link durchscheinen: another_stooge.nickname
// 'Moe'
// Die Löschung des nickname aus another_stooge // lässt den nickname des Prototyps durchscheinen. delete another_stooge.nickname; another_stooge.nickname
// 'Curly'
Reduzierung globaler Variablen JavaScript macht es einem leicht, globale Variablen zu definieren, die alle Elemente Ihrer Anwendung enthalten können. Unglücklicherweise schwächen globale Variablen die Geschmeidigkeit von Programmen und sollten daher vermieden werden.
Max. Linie
Max. Linie Reduzierung globaler Variablen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
27
Links Eine Möglichkeit, die Verwendung globaler Variablen zu minimieren, besteht darin, eine einzelne globale Variable für Ihre Anwendung anzulegen: var MYAPP = {};
Diese Variable wird dann der Container für Ihre Anwendung: MYAPP.stooge = { "first-name": "Joe", "last-name": "Howard" }; MYAPP.flight = { airline: "Oceanic", number: 815, departure: { IATA: "SYD", time: "2008-09-22 14:55", city: "Sydney" }, arrival: { IATA: "LAX", time: "2008-09-23 10:42", city: "Los Angeles" } };
Die Reduzierung Ihres globalen Profils auf einen einzelnen Namen reduziert die Wahrscheinlichkeit fehlerhafter Interaktionen mit anderen Anwendungen, Widgets oder Bibliotheken. Ihr Programm wird darüber hinaus besser lesbar, weil offensichtlich ist, dass MYAPP.stooge auf eine Top-Level-Struktur verweist. Im nächsten Kapitel zeigen wir Wege auf, Closures zum Information Hiding zu nutzen, was eine weitere effektive Technik zur Reduzierung globaler Variablen darstellt.
Max. Linie
Max. Linie 28
|
Kapitel 3: Objekte
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 4
KAPITEL 4
Funktionen
Why, every fault’s condemn’d ere it be done: Mine were the very cipher of a function… – William Shakespeare, Measure for Measure
Das Beste an JavaScript ist dessen Implementierung von Funktionen. Es wurde nahezu alles richtig gemacht, aber, wie man es bei JavaScript nicht anders erwartet, ist nicht alles perfekt. Eine Funktion fasst eine Reihe von Anweisungen zusammen. Funktionen bilden die grundlegende modulare Einheit von JavaScript. Sie werden verwendet, um Code wiederzuverwenden, um Informationen zu verstecken und um den Code zu strukturieren. Funktionen werden verwendet, um das Verhalten von Objekten festzulegen. Die Kunst der Programmierung besteht ganz allgemein darin, eine Reihe von Anforderungen auf eine Reihe von Funktionen und Datenstrukturen abzubilden.
Funktionsobjekte Funktionen sind bei JavaScript Objekte. Objekte sind Sammlungen von Name/ Wert-Paaren mit einer versteckten Verknüpfung zu einem Prototyp-Objekt. Aus Objektliteralen erzeugte Objekte sind mit Object.prototype verknüpft. Funktionsobjekte sind mit Function.prototype verknüpft (das selbst wiederum mit Object. prototype verknüpft ist. Jede Funktion wird darüber hinaus mit zwei zusätzlichen (versteckten) Eigenschaften erzeugt: dem Kontext der Funktion und dem Code, der das Verhalten der Funktion implementiert. Jedes Funktionsobjekt besitzt außerdem eine prototype-Eigenschaft. Ihr Wert ist ein Objekt mit einer constructor-Eigenschaft, deren Wert die Funktion ist. Diese unterscheidet sich von dem versteckten Link auf Function.prototype. Die Bedeutung dieser komplizierten Konstruktion wird im nächsten Kapitel deutlich.
Max. Linie
Weil Funktionen Objekte sind, können sie wie jeder andere Wert verwendet werden. Funktionen können in Variablen, Objekten und Arrays gespeichert werden.
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
29
Max. Linie
Links Funktionen können als Argumente an Funktionen übergeben werden, und Funktionen können Funktionen zurückgeben. Und weil Funktionen Objekte sind, können sie auch Methoden besitzen. Was Funktionen so besonders macht, ist die Tatsache, dass sie aufgerufen werden können.
Funktionsliterale Funktionsobjekte werden mit Funktionsliteralen erzeugt: // Variable namens add erzeugen und eine Funktion // darin ablegen, die zwei Zahlen addiert. var add = function (a, b) { return a + b; };
Ein Funktionsliteral besteht aus vier Teilen. Der erste Teil besteht aus dem reservierten Wort function. Der optionale zweite Teil ist der Name der Funktion. Die Funktion kann ihren Namen verwenden, um sich selbst rekursiv aufzurufen. Der Name kann auch von Debuggern und Entwicklungstools verwendet werden, um die Funktion zu identifizieren. Wird einer Funktion kein Name zugewiesen (wie oben geschehen), spricht man von einer anonymen Funktion. Den dritten Teil bilden die in Klammern stehenden Parameter der Funktion. Innerhalb der Klammer steht eine Reihe von Parameternamen, jeweils durch Kommata getrennt. Diese Namen werden innerhalb der Funktion als Variablen definiert. Im Gegensatz zu gewöhnlichen Variablen werden sie nicht mit undefined initialisiert, sondern mit den Argumenten, die beim Aufruf der Funktion übergeben werden. Den vierten Teil bilden die zwischen geschweiften Klammern stehenden Anweisungen. Diese Anweisungen bilden den Rumpf (Body) der Funktion. Sie werden ausgeführt, wenn die Funktion aufgerufen wird. Ein Funktionsliteral kann überall da stehen, wo ein Ausdruck stehen kann. Funktionen können innerhalb anderer Funktionen definiert werden. Eine solche geschachtelte Funktion hat natürlich Zugriff auf ihre Parameter und Variablen. Eine innere Funktion kann aber auch auf die Parameter und Variablen der Funktion zugreifen, in der sie definiert ist. Das durch ein Funktionsliteral erzeugte Funktionsobjekt enthält einen Link auf diesen äußeren Kontext. Das wird als Closure bezeichnet und ist die Quelle enormer Ausdrucksstärke.
Max. Linie
Max. Linie 30
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Aufruf Der Aufruf einer Funktion unterbricht die Ausführung der aktuellen Funktion und übergibt die Parameter und die Kontrolle an die neue Funktion. Zusätzlich zu den deklarierten Parametern erhält jede Funktion zwei zusätzliche Parameter: this und arguments. Der Parameter this ist für die objektorientierte Programmierung sehr wichtig, und sein Wert wird durch das Aufrufmuster bestimmt. Es gibt in JavaScript vier Muster für den Aufruf: das Methoden-Aufrufmuster, das Funktions-Aufrufmuster, das Konstruktor-Aufrufmuster und das Apply-Aufrufmuster. Die Muster unterscheiden sich in der Form, wie der Bonusparameter this initialisiert wird. Der Aufrufoperator besteht aus einem Paar Klammern, die auf einen Ausdruck folgen, der einen Funktionswert erzeugt. Die Klammern können null oder mehr durch Kommata getrennte Ausdrücke enthalten. Jeder Ausdruck erzeugt einen Argumentwert. Jeder dieser Argumentwerte wird den entsprechenden Parameternamen der Funktion zugewiesen. Es gibt keinen Laufzeitfehler, wenn die Anzahl der Argumente nicht mit der Anzahl der Parameter übereinstimmt. Gibt es zu viele Argumente, werden die überzähligen Argumente einfach ignoriert. Gibt es zu wenige, werden die fehlenden Argumentwerte mit undefined aufgefüllt. Es gibt keine Typprüfung der Argumentwerte: Jeder Wertetyp kann an jeden Parameter übergeben werden.
Muster für den Methodenaufruf Wird eine Funktion als Eigenschaft eines Objekts gespeichert, bezeichnen wir sie als Methode. Beim Aufruf einer Methode wird this an dieses Objekt gebunden. Enthält der Funktionsaufruf eine Spezifizierung (d.h. einen Punkt-Ausdruck oder einen [index]-Ausdruck), wird er als Methode aufgerufen: // // // //
Erzeuge myObject. Es besitzt einen Wert und eine increment-Methode. Die increment-Methode besitzt einen optionalen Parameter. Ist das Argument keine Zahl, wird 1 als Standardwert verwendet.
var myObject = { value: 0, increment: function (inc) { this.value += typeof inc === 'number' ? inc : 1; } };
Max. Linie
myObject.increment( ); document.writeln(myObject.value);
// 1
myObject.increment(2); document.writeln(myObject.value);
// 3
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Aufruf
|
31
Links Eine Methode kann this verwenden, um auf das Objekt zuzugreifen (d.h., um Werte aus dem Objekt abzurufen) bzw. das Objekt zu modifizieren. Die Bindung von this an das Objekt erfolgt erst beim Aufruf. Diese sehr späte Bindung macht Funktionen, die this nutzen, hochgradig wiederverwendbar. Methoden, die ihren Objektkontext von this erhalten, werden öffentliche Methoden genannt.
Muster für den Funktionsaufruf Ist eine Funktion keine Eigenschaft eines Objekts, wird sie als Funktion aufgerufen: var sum = add(3, 4);
// sum ist 7
Wird eine Funktion nach diesem Muster aufgerufen, wird this an das globale Objekt gebunden, was ein Fehler im Design der Sprache ist. Wäre die Sprache sauber entworfen worden, würde beim Aufruf einer inneren Funktion this zur thisVariable der äußeren Funktion werden. Eine Konsequenz dieses Fehlers besteht darin, dass eine Methode eine innere Funktion nicht für ihre Arbeit nutzen kann, weil die innere Funktion nicht den gleichen Zugriff wie die Methode besitzt, da deren this an den falschen Wert gebunden ist. Glücklicherweise gibt es einen einfachen Workaround. Definiert eine Methode eine Variable und weist sie ihr den Wert von this zu, hat die innere Funktion über diese Variable Zugriff auf this. Per Konvention lautet der Name dieser Variablen that: // myObject um eine double-Methode erweitern. myObject.double = function ( ) { var that = this; // Workaround. var helper = function ( ) { that.value = add(that.value, that.value); }; helper( );
// helper als Funktion aufrufen.
}; // double als Methode aufrufen. myObject.double( ); document.writeln(myObject.getValue( ));
// 6
Muster für den Konstruktoraufruf JavaScript ist eine Sprache mit prototypischer Vererbung. Das bedeutet, dass Objekte Eigenschaften direkt von anderen Objekten erben können. Die Sprache kennt keine Klassen.
Max. Linie
Das ist eine radikale Abkehr von der momentanen Mode. Die meisten heutigen Sprachen arbeiten mit Klassen. Die prototypische Vererbung ist sehr ausdrucks-
32
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts stark, wird aber nicht gut verstanden. JavaScript selbst besteht nicht auf seiner prototypischen Natur, sondern besitzt auch eine objektgenerierende Syntax – ein Tribut an die Sprachen, die mit Klassen arbeiten. Nur wenige klassenorientiert geschulte Programmierer halten die prototypische Vererbung für akzeptablel, und von Klassen inspirierte Syntax verschleiert die wahre prototypische Natur der Sprache. Das ist die Schlimmste beider Welten. Wird eine Funktion mit dem Präfix new aufgerufen, wird ein neues Objekt erzeugt, das einen versteckten Link auf den Wert des prototype-Members der Funktion enthält, und this wird an das neue Objekt gebunden. Das new-Präfix verändert auch das Verhalten der return-Anweisung. Mehr darüber erfahren Sie gleich. // Konstruktorfunktion namens Quo erzeugen. // Sie erzeugt ein Objekt mit einer status-Eigenschaft. var Quo = function (string) { this.status = string; }; // Gibt allen Instanzen von Quo eine öffentliche // Methode namens get_status. Quo.prototype.get_status = function ( ) { return this.status; }; // Erzeuge eine Instanz von Quo. var myQuo = new Quo("confused"); document.writeln(myQuo.get_status( ));
// confused
Für den Einsatz mit dem new-Präfix gedachte Funktionen werden als Konstruktoren bezeichnet. Per Konvention werden sie in Variablen vorgehalten, deren Name mit einem Großbuchstaben beginnt. Wird ein Konstruktor ohne das new-Präfix aufgerufen, geschehen ohne irgendwelche Compiler- oder Laufzeitwarnungen sehr schlimme Dinge, weshalb die Großschreibkonvention wirklich wichtig ist. Die Verwendung dieses Konstruktorstils ist nicht empfehlenswert. Im nächsten Kapitel werden Sie bessere Alternativen kennenlernen.
Muster für den Aufruf der Apply-Methode Da JavaScript eine funktional objektorientierte Sprache ist, können Funktionen Methoden besitzen.
Max. Linie
Die apply-Methode erlaubt uns die Konstruktion eines Arrays von Argumenten, die zum Aufruf einer Funktion verwendet werden. Sie ermöglicht es uns auch, den
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Aufruf
|
33
Max. Linie
Links Wert von this zu wählen. Die apply-Methode verlangt zwei Parameter. Der erste ist der Wert, den Sie an this binden müssen. Der zweite ist ein Array von Parametern. // Array aus zwei Zahlen erzeugen und diese addieren. var array = [3, 4]; var sum = add.apply(null, array);
// sum ist 7
// Erzeuge ein Objekt mit einem status-Member. var statusObject = { status: 'A-OK' }; // // // //
statusObject erbt nicht von Quo.prototype, aber wir können die get_status-Methode für statusObject aufrufen, obwohl statusObject keine get_status-Methode besitzt.
var status = Quo.prototype.get_status.apply(statusObject); // status ist 'A-OK'
Argumente Das arguments-Array ist ein Bonusparameter, der Funktionen bei ihrem Aufruf zur Verfügung steht. Es erlaubt der Funktion den Zugriff auf alle Argumente, die beim Aufruf übergeben wurden, einschließlich überzähliger Argumente, die nicht an Parameter übergeben wurden. Das ermöglicht die Entwicklung von Funktionen, die eine variable Anzahl von Parametern verarbeiten können: // Funktion, die sehr viele Dinge addiert. // // // //
Beachten Sie, dass die Definition der Variablen sum innerhalb der Funktion nicht mit dem außerhalb der Funktion definierten sum kollidiert. Die Funktion sieht nur das innere sum.
var sum = function ( ) { var i, sum = 0; for (i = 0; i < arguments.length; i += 1) { sum += arguments[i]; } return sum; }; document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108
Max. Linie
Das ist kein besonders nützliches Muster. In Kapitel 6 werden Sie sehen, wie man ein Array um eine ähnliche Methode erweitert. Aufgrund eines Designfehlers ist arguments in Wirklichkeit kein Array, sondern ein Array-artiges Objekt. arguments besitzt eine length-Eigenschaft, ihm fehlen aber
34
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts sämtliche Array-Methoden. Eine Folge dieses Designfehlers werden Sie am Ende dieses Kapitels sehen.
Return Wird eine Funktion aufgerufen, beginnt die Ausführung mit der ersten Anweisung und endet, sobald das } erreicht wird, das den Funktionsrumpf abschließt. Die Funktion übergibt die Kontrolle dann wieder an den Teil des Programms, der die Funktion aufgerufen hat. Die return-Anweisung kann verwendet werden, um eine Funktion frühzeitig zu beenden. Wird return ausgeführt, kehrt die Funktion sofort zurück, ohne die restlichen Anweisungen auszuführen. Eine Funktion gibt immer einen Wert zurück. Ohne Angabe eines return-Werts wird undefined zurückgegeben. Wenn die Funktion mit dem new-Präfix aufgerufen wird und der return-Wert kein Objekt ist, dann wird stattdessen this (das neue Objekt) zurückgegeben.
Ausnahmen JavaScript stellt einen Mechanismus zur Behandlung von Ausnahmen (Exceptions) zur Verfügung. Ausnahmen sind ungewöhnliche (aber nicht völlig unerwartete) Pannen, die den noralen Programmfluss stören. Wird eine solche Panne erkannt, sollte Ihr Programm eine Ausnahme auslösen: var add = function (a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw { name: 'TypeError', message: 'add needs numbers' }; } return a + b; }
Die throw-Anweisung unterbricht die Ausführung der Funktion. Ihr muss ein exception-Objekt übergeben werden, das eine name-Eigenschaft besitzt, die den Typ der Ausnahme identifiziert, sowie eine beschreibende message-Eigenschaft. Sie können auch zusätzliche Eigenschaften hinzufügen. Das exception-Objekt wird an die catch-Klausel einer try-Anweisung übergeben: // try_it-Funktion, die die neue add-Funktion // falsch aufruft.
Max. Linie
Max. Linie
var try_it = function ( ) {
Ausnahmen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
35
Links try { add("seven"); } catch (e) { document.writeln(e.name + ': ' + e.message); } } try_it( );
Wird eine Ausnahme innerhalb eines try-Blocks ausgelöst, geht die Kontrolle an dessen catch-Klausel über. Eine try-Anweisung besitzt einen einzelnen catch-Block, der alle Ausnahmen abfängt. Hängt die Verarbeitung vom Typ der Ausnahme ab, muss der ExceptionHandler den namen untersuchen, um den Typ der Ausnahme zu ermitteln.
Typen erweitern JavaScript erlaubt es, grundlegende Typen der Sprache zu erweitern. In Kapitel 3 haben Sie gesehen, dass das Hinzufügen einer Methode zu Object.prototype diese Methode für alle Objekte verfügbar macht. Das funktioniert auch für Funktionen, Arrays, Strings, Zahlen, reguläre Ausdrücke und Boolesche Werte. Indem man beispielsweise Function.prototype erweitert, kann man eine Methode allen Funktionen zur Verfügung stellen: Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; };
Indem wir Function.prototype mit der Methode method erweitern, müssen wir nicht länger den Namen der prototype-Eigenschaft eingeben. Dieser etwas hässliche Teil kann jetzt versteckt werden. JavaScript besitzt keinen separaten Integertyp, weshalb es manchmal notwendig ist, nur den ganzzahligen Teil einer Zahl herauszufiltern. Die zu diesem Zweck von JavaScript bereitgestellte Methode ist hässlich. Wir können das korrigieren, indem wir eine integer-Methode zu Number.prototype hinzufügen. Diese verwendet (je nach Vorzeichen der Zahl) entweder Math.ceiling oder Math.floor: Number.method('integer', function ( ) { return Math[this < 0 ? 'ceiling' : 'floor'](this); }); document.writeln((-10 / 3).integer( ));
Max. Linie
// -3
JavaScript fehlt eine Methode, die Leerzeichen vom Ende des Strings entfernt. Das lässt sich leicht korrigieren:
36
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts String.method('trim', function ( ) { return this.replace(/^\s+|\s+$/g, ''); }); document.writeln('"' + "
neat
".trim( ) + '"');
Unsere trim-Methode verwendet einen regulären Ausdruck. Mehr über reguläre Ausdrücke erfahren Sie in Kapitel 7. Durch die Erweiterung grundlegender Typen können wir die Ausdrucksfähigkeit der Sprache deutlich verbessern. Aufgrund der dynamischen Natur der prototypischen Vererbung von JavaScript werden alle Werte mit den neuen Methoden ausgestattet, selbst wenn diese erzeugt wurden, bevor die Methoden zur Verfügung standen. Die Prototypen der grundlegenden Typen sind öffentliche Strukturen, weshalb man beim Mischen von Bibliotheken vorsichtig sein muss. Eine defensive Technik besteht darin, eine Methode nur dann hinzuzufügen, wenn diese fehlt: // Methode bedingt hinzufügen. Function.prototype.method = function (name, func) { if (!this.prototype[name]) { this.prototype[name] = func; } };
Ein weiterer Aspekt ist die Tatsache, dass die for...in-Anweisung schlecht mit Prototypen interagiert. Wir haben in Kapitel 3 einige Möglichkeiten gesehen, um dies zu umgehen: Wir können die hasOwnProperty-Methode verwenden, um vererbte Eigenschaften aufzudecken, und wir können nach bestimmten Typen suchen.
Rekursion Eine rekursive Funktion ist eine Funktion, die sich (direkt oder indirekt) selbst aufruft. Rekursion ist eine sehr leistungsfähige Programmiertechnik, bei der ein Problem in eine Reihe gleichartiger Teilprobleme zerlegt wird und jedes mit einem trivialen Ansatz gelöst wird. Eine rekursive Funktion ruft sich dabei selbst auf, um die Teilprobleme zu lösen. Die Türme von Hanoi sind ein berühmtes Knobelspiel. Es besteht aus drei Stäben und einer Reihe von Scheiben unterschiedlicher Größe mit einem Loch in der Mitte. Zu Beginn werden alle Scheiben (nach Größe sortiert) auf einem Stab angeordnet. Das Ziel besteht darin, diesen Stapel auf einen anderen Stab zu bewegen, indem man jeweils eine Scheibe verschiebt ohne eine größere Scheibe auf eine kleinere zu legen. Dieses Spiel besitzt eine triviale rekursive Lösung:
Max. Linie
Max. Linie Rekursion | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
37
Links var hanoi = function (disc, src, aux, dst) { if (disc > 0) { hanoi(disc - 1, src, dst, aux); document.writeln('Bewege Scheibe ' + disc + ' von ' + src + ' nach ' + dst); hanoi(disc - 1, aux, src, dst); } }; hanoi(3, 'Quelle', 'Helfer', 'Ziel');
Sie erzeugt bei drei Scheiben die folgende Lösung: Bewege Bewege Bewege Bewege Bewege Bewege Bewege
Scheibe Scheibe Scheibe Scheibe Scheibe Scheibe Scheibe
1 2 1 3 1 2 1
von von von von von von von
Quelle nach Ziel Quelle nach Helfer Ziel nach Helfer Quelle nach Ziel Helfer nach Quelle Helfer nach Ziel Quelle nach Ziel
Die hanoi-Funktion bewegt einen Stapel mit Scheiben von einem Stab zum anderen und nutzt bei Bedarf einen Hilfsstab. Sie zerlegt das Problem in drei Unterprobleme. Zuerst legt sie die untere Scheibe frei, indem sie den darüber liegenden Stapel auf den Hilfsstab bewegt. Sie kann dann die unterste Scheibe auf den Zielstab schieben. Zum Schluss kann sie den Teilstapel vom Hilfsstab auf den Zielstab bewegen. Die Bewegung des Teilstapels wird erledigt, indem sich die Funktion selbst aufruft, um diese Teilprobleme zu lösen. Der hanoi-Funktion wird die Anzahl der Scheiben und die drei zu verwendenden Stäbe übergeben. Ruft sie sich selbst auf, verarbeitet sie die Scheibe, die über der liegt, an der sie gerade arbeitet. Schließlich erfolgt ein Aufruf mit einer nicht existierenden Scheibenzahl. In diesem Fall geschieht nichts. Dieser Akt des Nichtstuns gibt uns die Sicherheit, dass die Funktion sich nicht endlos selbst aufruft. Rekursive Funktionen können sehr effektiv sein, wenn es um die Manipulation von Baumstrukturen wie etwa dem Document Object Model (DOM) des Browsers geht. Jedem rekursiven Aufruf wird ein kleinerer Teil des Baums übergeben, an dem gearbeitet werden soll: // // // // // //
Definition einer walk_the_DOM-Funktion, die jeden Knoten des Baums in HTML-Quellanordnung besucht, ausgehend von irgendeinem gegebenen Knoten. Sie ruft eine Funktion auf und übergibt ihr nacheinander jeden Knoten. walk_the_DOM ruft sich selbst auf, um alle Kindknoten zu verarbeiten.
var walk_the_DOM = function walk(node, func) { func(node); node = node.firstChild;
Max. Linie 38
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts while (node) { walk(node, func); node = node.nextSibling; } }; // // // // // //
Definition einer getElementsByAttribute-Funktion. Sie verlangt einen Attributnamen-String und einen optionalen Matching-Wert. Sie ruft walk_the_DOM auf und übergibt ihr eine Funktion, die nach einem Attributnamen in einem Knoten sucht. Die übereinstimmenden Knoten werden in einem Ergebnis-Array zusammengefasst.
var getElementsByAttribute = function (att, value) { var results = []; walk_the_DOM(document.body, function (node) { var actual = node.nodeType === 1 && node.getAttribute(att); if (typeof actual === 'string' && (actual === value || typeof value !== 'string')) { results.push(node); } }); return results; };
Einige Sprachen bieten eine Optimierung in Form der sogenannten Tail Recursion. Das bedeutet: Wenn eine Funktion das Ergebnis zurückliefert, indem sie sich rekursiv selbst aufruft, dann wird dieser Aufruf durch eine Schleife ersetzt, was die Ausführungsgeschwindigkeit deutlich erhöht. Leider bietet JavaScript diese Form der Optimierung momentan nicht an. Funktionen mit sehr hoher Rekursionstiefe können fehlschlagen, da sie den Rückkehr-Stack ausreizen: // factorial-Funktion mit Tail Recursion. // Sie ist Tail Recursive, da sie das Ergebnis // des eigenen Aufrufs zurückgibt. // JavaScript optimiert diese Form momentan nicht. var factorial = function factorial(i, a) { a = a || 1; if (i < 2) { return a; } return factorial(i - 1, a * i); }; document.writeln(factorial(4));
// 24
Max. Linie
Max. Linie Rekursion | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
39
Links Geltungsbereich Der Geltungsbereich (Scope) einer Programmiersprache kontrolliert die Sichtbarkeit und Lebensdauer von Variablen und Parametern. Das ist für den Programmierer wichtig, da dieses Konzept Namenskollisionen reduziert und ein automatisches Speichermanagement bereitstellt: var foo = function ( ) { var a = 3, b = 5; var bar = function ( ) { var b = 7, c = 11; // An diesem Punkt ist a gleich 3, b gleich 7 und c gleich 11 a += b + c; // An diesem Punkt ist a gleich 21, b gleich 7 und c gleich 11 }; // An diesem Punkt ist a gleich 3, b gleich 5 und c nicht definiert bar( ); // An diesem Punkt ist a gleich 21 und b gleich 5 };
Die meisten Sprachen mit C-Syntax besitzen einen Block-Geltungsbereich. Alle Variablen, die innerhalb eines Blocks (einer von geschweiften Klammern umschlossenen Gruppe von Anweisungen) definiert sind, sind außerhalb dieses Blocks nicht sichtbar. Die innerhalb des Blocks definierten Variablen können freigegeben werden, wenn die Ausführung des Blocks abgeschlossen ist. Das ist eine gute Sache. Unglücklicherweise besitzt JavaScript keinen Block-Geltungsbereich, auch wenn die Block-Syntax einem suggeriert, dass es so ist. Das kann eine Quelle für Fehler sein. JavaScript verwendet einen Funktions-Geltungsbereich. Das bedeutet, dass die in einer Funktion definierten Parameter und Variablen außerhalb der Funktion nicht sichtbar sind, während die definierten Variablen überall innerhalb der Funktion sichtbar sind.
Max. Linie
Bei vielen modernen Sprachen wird empfohlen, Variablen so spät wie möglich zu deklarieren, also bei ihrer ersten Verwendung. Für JavaScript ist das kein guter Rat, weil es keinen Block-Geltungsbereich kennt. Daher ist es besser, alle in einer Funktion verwendeten Variablen zu Beginn der Funktion zu deklarieren.
40
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Closure Das Gute am Geltungsbereich ist, dass innere Funktionen Zugriff auf die Parameter und Variablen der Funktionen haben, in denen sie definiert sind (mit der Ausnahme von this und arguments). Das ist eine sehr gute Sache. Unsere getElementsByAttribute-Funktion funktioniert, weil sie eine results-Variable deklariert und weil die innere Funktion, die an walk_the_DOM übergeben wird, Zugriff auf diese results-Variable hat. Ein interessanterer Fall tritt dann ein, wenn die innere Funktion eine längere Lebensdauer hat als die äußere Funktion. Vorhin haben wir ein myObject angelegt, das einen Wert (value) und eine incrementMethode besitzt. Nehmen wir mal an, wir wollen den Wert vor nicht autorisierten Änderungen schützen. Anstatt myObject mit einem Objektliteral zu initialisieren, können wir myObject initialisieren, indem wir eine Funktion aufrufen, die ein Objektliteral zurückgibt. Diese Funktion definiert eine value-Variable. Diese Variable ist für die increment- und getValue-Methoden immer verfügbar, aber der Funktions-Geltungsbereich sorgt dafür, dass sie vor dem Rest des Programms verborgen bleibt: var myObject = function ( ) { var value = 0; return { increment: function (inc) { value += typeof inc === 'number' ? inc : 1; }, getValue: function ( ) { return value; } }; }( );
Wir weisen myObject keine Funktion zu, sondern das Ergebnis des Aufrufs dieser Funktion. Beachten Sie die ( ) in der letzten Zeile. Die Funktion gibt ein Objekt zurück, das zwei Methoden enthält, und diese Methoden genießen das Recht, auf die value-Variable zugreifen zu können. Der Quo-Konstruktur vom Anfang dieses Kapitels erzeugte ein Objekt mit einer status-Eigenschaft und einer get_status-Methode. Aber das ist eigentlich uninteressant. Warum sollte man eine Getter-Methode für eine Eigenschaft nutzen, auf die man direkt zugreifen kann? Das wäre nützlicher, wenn die status-Eigenschaft eine private Eigenschaft wäre. Lassen Sie uns also eine quo-Funktion definieren, bei der das so ist:
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Closure
|
41
Links // Konstruktor namens quo. Erzeugt ein Objekt // mit einer get_status-Methode und einer // privaten status-Eigenschaft. var quo = function (status) { return { get_status: function ( ) { return status; } }; }; // Instanz von quo erzeugen. var myQuo = quo("amazed"); document.writeln(myQuo.get_status( ));
Diese quo-Funktion wurde entworfen, um ohne das new-Präfix verwendet zu werden, so dass der Name nicht mit einem Großbuchstaben beginnt. Rufen wir quo auf, liefert die Funktion ein neues Objekt zurück, das eine get_status-Methode enthält. Eine Referenz auf dieses Objekt wird in myQuo gespeichert. Die get_status-Methode darf auf die status-Eigenschaft von quo zugreifen, obwohl quo bereits zurückgekehrt ist. get_status besitzt keinen Zugriff auf eine Kopie des Parameters, sondern auf den Parameter selbst. Das ist möglich, weil die Funktion Zugriff auf den Kontext besitzt, in dem sie erzeugt wurde. Das wird als Closure bezeichnet. Sehen wir uns ein etwas nützlicheres Beispiel an: // Eine Funktion, die die Farbe eines DOM-Knotens auf // Gelb setzt und dann zu Weiß überblendet. var fade = function (node) { var level = 1; var step = function ( ) { var hex = level.toString(16); node.style.backgroundColor = '#FFFF' + hex + hex; if (level < 15) { level += 1; setTimeout(step, 100); } }; setTimeout(step, 100); }; fade(document.body);
Max. Linie
Wir rufen fade auf und übergeben ihr document.body (der Knoten, der durch das HTML-Tag erzeugt wurde). fade setzt level auf 1. Sie definiert eine stepFunktion. Sie ruft setTimeout auf und übergibt dabei die step-Funktion und einen Zeitwert (100 Millisekunden). Dann kehrt sie zurück – fade wurde abgeschlossen.
42
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Etwa eine Zehntelsekunde später wird die step-Funktion aufgerufen. Sie erzeugt ein Hex-Zeichen aus dem fade-level. Sie modifiziert dann die Hintergrundfarbe des fade-Knotens. Dann sieht sie sich den fade-level an. Ist dieser noch nicht Weiß, inkrementiert sie den level und verwendet setTimeout, um sich selbst erneut auszuführen. Die step-Funktion wird erneut aufgerufen, aber diesmal liegt fades level bei 2. fade ist schon vor einer Weile zurückgekehrt, aber ihre Variablen leben weiter, solange sie von einer oder mehreren inneren Funktionen von fade noch benötigt werden. Es ist wichtig zu verstehen, dass die innere Funktion Zugriff auf die eigentlichen Variablen der äußeren Funktion hat und nicht bloß auf eine Kopie. Nur so können Sie das folgende Problem vermeiden: // SCHLECHTES BEISPIEL // // // // //
Eine Funktion, die Eventhandler-Funktionen auf die falsche Weise an ein Array von Knoten zuweist. Wenn Sie einen Knoten anklicken, soll eine Alarmbox die Ordinalzahl des Knotens ausgeben. Stattdessen wird aber immer die Anzahl der Knoten ausgegeben.
var add_the_handlers = function (nodes) { var i; for (i = 0; i < nodes.length; i += 1) { nodes[i].onclick = function (e) { alert(i); }; } };
Die Funktion add_the_handlers war eigentlich dazu gedacht, jedem Handler eine eindeutige Nummer (i) zuzuweisen. Das schlägt fehl, weil die Handlerfunktionen an die Variable i gebunden werden und nicht an den Wert der Variablen i beim Funktionsaufruf: // BESSERES BEISPIEL // // // //
Max. Linie
Diese Funktion weist Eventhandler-Funktionen in der richtigen Weise an ein Array von Knoten zu. Wenn Sie einen Knoten anklicken, erscheint eine Alarmbox mit der Ordinalzahl des Knotens.
var add_the_handlers = function (nodes) { var i; for (i = 0; i < nodes.length; i += 1) { nodes[i].onclick = function (i) { return function (e) { alert(e); }; }(i);
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Closure
|
43
Links } }; // ENDE BESSERES BEISPIEL
Anstatt eine Funktion an onclick zuzuweisen, definieren wir nun eine Funktion und rufen diese mit i sofort auf. Diese Funktion gibt eine Eventhandler-Funktion zurück, die an den übergebenen Wert von i gebunden ist und nicht an das in add_ the_handlers definierte i. Diese zurückgegebene Funktion wird dann an onclick zugewiesen.
Callbacks Funktionen können die Arbeit mit diskontinuierlichen Ereignissen vereinfachen. Nehmen wir zum Beispiel an, es gebe eine Arbeitssequenz, die mit der BenutzerInteraktion beginnt, einen Request an den Server sendet und schließlich die Antwort des Servers ausgibt. Der naive Ansatz würde wie folgt aussehen: request = request_vorbereiten( ); response = request_synchron_senden(request); display(response);
Das Problem mit diesem Ansatz besteht darin, dass der synchrone Request über das Netzwerk den Client in einem eingefrorenen Zustand hinterlässt. Ist das Netzwerk oder der Server langsam, wird das Ansprechverhalten der Anwendung inakzeptabel langsam. Ein besserer Ansatz besteht in einem asynchronen Request, der eine Callback-Funktion zur Verfügung stellt, die aufgerufen wird, wenn die Antwort des Servers empfangen wird. Eine asynchrone Funktion kehrt sofort zurück, so dass der Client nicht blockiert wird: request = request_vorbereiten( ); request_asynchron_senden(request, function (response) { display(response); });
Wir übergeben einen Funktionsparameter an request_asynchron_senden, der aufgerufen wird, wenn die Response eingeht.
Module
Max. Linie
Wir können Funktionen und Closures nutzen, um Module aufzubauen. Ein Modul ist eine Funktion oder ein Objekt, die bzw. das eine Schnittstelle bereitstellt, aber ihren Zustand und ihre Implementierung versteckt. Indem wir Funktionen für den Aufbau von Modulen nutzen, können wir die Verwendung globaler Variablen nahezu vollständig aufgeben und so eines der schlechtesten Merkmale von JavaScript umgehen.
44
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Nehmen wir zum Beispiel an, dass wir String um eine deentityify-Methode erweitern wollen. Ihre Aufgabe besteht darin, in einem String nach HTML-Entitäten zu suchen und diese durch entsprechende Äquivalente zu ersetzen. Es ist sinnvoll, die Namen der Entitäten und deren Äquivalente in einem Objekt vorzuhalten. Aber wohin mit dem Objekt? Wir könnten es in eine globale Variable packen, aber globale Variablen sind böse. Wir könnten es in der Funktion selbst definieren, aber das wirkt sich auf die Laufzeit aus, weil das Literal jedes Mal evaluiert werden muss, wenn die Funktion aufgerufen wird. Der ideale Ansatz besteht darin, das Objekt in eine Closure zu packen und vielleicht noch eine zusätzliche Methode bereitzustellen, mit der weitere Entitäten hinzugefügt werden können: String.method('deentityify', function ( ) { // Die Entitäten-Tabelle. Sie bildet Entitätsnamen // auf Zeichen ab. var entity = { quot: '"', lt: '<', gt: '>' }; // deentityify-Methode zurückgeben. return function ( ) { // // // // // //
deentityify-Methode. Sie ruft die String-Ersetzungsmethode auf und sucht nach Teil-Strings, die mit '&' beginnen und mit ';' enden. Sind die Zeichen dazwischen in der Entitätstabelle enthalten, wird die Entität durch das Zeichen in der Tabelle ersetzt. Sie verwendet einen regulären Ausdruck (Kapitel 7). return this.replace(/&([^&;]+);/g, function (a, b) { var r = entity[b]; return typeof r === 'string' ? r : a; } );
}; }( ));
Beachten Sie die letzte Zeile. Wir rufen die gerade angelegte Funktion mit dem Operator ( ) direkt auf. Dieser Aufruf erzeugt die Funktion, die zur deentityify-Methode wird, und liefert diese zurück. document.writeln( '<">'.deentityify( ));
// <">
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Module
|
45
Links Das Modulmuster nutzt die Vorteile von Funktions-Geltungsbereichen und Closures, um Beziehungen aufzubauen, die bindend und privat sind. In diesem Beispiel hat nur die deentityify-Methode Zugriff auf die Entitäts-Datenstruktur. Das allgemeine Muster für ein Modul ist eine Funktion, die private Variablen und Funktionen definiert. Man baut privilegierte Funktionen über Closures auf, die Zugriff auf private Variablen und Funktionen haben. Diese liefern die privilegierten Funktionen dann zurück oder speichern sie an einem zugänglichen Ort. Die Verwendung dieses Modulmusters kann den Einsatz globaler Variablen verhindern. Es unterstützt das Information Hiding und andere gute Designpraktiken. Es ist bei der Kapselung von Anwendungen und anderen Einheiten sehr effektiv. Es kann auch verwendet werden, um sichere Objekte zu erzeugen. Nehmen wir an, Sie benötigen ein Objekt, das eine Seriennummer erzeugt: var serial_maker = function ( ) { // // // // // //
Erzeuge ein Objekt, das eindeutige Strings produziert. Ein eindeutiger String besteht aus zwei Teilen: einem Präfix und einer Sequenznummer. Das Objekt besitzt Methoden zum Setzen des Präfixes und der Sequenznummer sowie eine gensym-Methode, die einen eindeutigen String generiert. var prefix = ''; var seq = 0; return { set_prefix: function (p) { prefix = String(p); }, set_seq: function (s) { seq = s; }, gensym: function ( ) { var result = prefix + seq; seq += 1; return result; } };
}; var seqer = serial_maker( ); seqer.set_prefix('Q'); seqer.set_seq(1000); var unique = seqer.gensym( );
Max. Linie
// unique ist "Q1000"
Die Methoden verwenden weder this noch that. Es gibt daher keine Möglichkeit, seqer zu kompromittieren. Außer über die bereitgestellten Methoden gibt es keine Möglichkeit, prefix oder seq zu ändern. Das seqer-Objekt ist variabel, d.h., die Me-
46
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts thoden könnten ersetzt werden, aber das gewährt einem keinen Zugriff auf ihre Geheimnisse. seqer ist einfach eine Sammlung von Funktionen, und diese Funktionen besitzen Fähigkeiten, die die Verwendung oder Modifikation des geheimen Zustands erlauben. Würden wir seqer.gensym an eine andere Funktion übergeben, könnte diese Funktion eindeutige Strings generieren. Sie wäre aber nicht in der Lage, prefix oder seq zu verändern.
Kaskaden Einige Methoden besitzen keinen Rückgabewert. Zum Beispiel ist es für Methoden, die den Zustand eines Objekts setzen oder ändern, üblich, nichts zurückzuliefern. Liefern diese Methoden this statt undefined zurück, können wir Kaskaden aktivieren. Bei einer Kaskade können wir in einer einzigen Anweisung viele Methoden nacheinander für das gleiche Objekt aufrufen. Eine Ajax-Bibliothek, die Kaskaden ermöglicht, würde es uns erlauben, den folgenden Stil zu verwenden: getElement('myBoxDiv'). move(350, 150). width(100). height(100). color('red'). border('10px outset'). padding('4px'). appendText("Please stand by"). on('mousedown', function (m) { this.startDrag(m, this.getNinth(m)); }). on('mousemove', 'drag'). on('mouseup', 'stopDrag'). later(2000, function ( ) { this. color('yellow'). setHTML("What hath God wraught?"). slide(400, 40, 200, 200); }). tip('This box is resizeable');
In diesem Beispiel erzeugt die getElement-Funktion ein Objekt, das dem DOM-Element mit der id="myBoxDiv" eine gewisse Funktionalität gibt. Die Methoden erlauben es uns, das Element zu bewegen, seine Größe und den Stil zu ändern und Verhaltensweisen hinzuzufügen. Jede dieser Methoden gibt das Objekt zurück, so dass das Ergebnis des Aufrufs für den nächsten Aufruf verwendet werden kann.
Max. Linie
Kaskaden können sehr ausdrucksstarke Schnittstellen erzeugen. Sie können dazu beitragen, uns davon abzuhalten, Schnittstellen aufzubauen, die versuchen, zu viele Dinge auf einmal zu tun.
Kaskaden | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
47
Max. Linie
Links Curry Funktionen sind Werte, und wir können Funktionswerte auf interessante Weise manipulieren. Das sogenannte Currying erlaubt es uns, eine neue Funktion zu erzeugen, indem wir eine Funktion und ein Argument kombinieren: var add1 = add.curry(1); document.writeln(add1(6));
// 7
add1 ist eine Funktion, die erzeugt wurde, indem eine 1 an die curry-Methode von add übergeben wurde. Die Funktion add1 addiert 1 zu ihrem Argument hinzu. JavaScript besitzt keine curry-Methode, aber das können wir korrigieren, indem wir Function.prototype erweitern: Function.method('curry', function ( ) { var args = arguments, that = this; return function ( ) { return that.apply(null, args.concat(arguments)); }; }); // Irgendetwas stimmt nicht...
Die curry-Methode erzeugt eine Closure, die die Originalfunktion und die an curry übergebenen Argumente enthält. Sie gibt eine Funktion zurück, die beim Aufruf die Originalfunktion zusammen mit den Argumenten des curry-Aufrufs und des aktuellen Aufrufs ausführt und das entsprechende Ergebnis zurückliefert. Sie verwendet die Array-Methode concat, um die beiden Argument-Arrays zu verketten. Wie Sie bereits gesehen haben, ist arguments aber gar kein Array und besitzt daher auch keine concat-Methode. Um das zu umgehen, wenden wir die Array-Methode slice auf beide arguments-Arrays an. Damit erzeugen wir Arrays, die sich mit der concat-Methode korrekt verhalten: Function.method('curry', function ( ) { var slice = Array.prototype.slice, args = slice.apply(arguments), that = this; return function ( ) { return that.apply(null, args.concat(slice.apply(arguments))); }; });
Memoization Funktionen können Objekte nutzen, um sich die Ergebnisse vorangegangener Operationen zu merken. Dadurch lässt sich unnötige Arbeit vermeiden. Diese Optimierung wird als Memoization bezeichnet. JavaScript-Objekte und Arrays eignen sich hierzu hervorragend.
Max. Linie
Max. Linie 48
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Nehmen wir an, Sie benötigen eine rekursive Funktion zur Berechnung von Fibonacci-Zahlen. Eine Fibonacci-Zahl ist die Summe zweier vorangegangener Fibonacci-Zahlen. Die ersten beiden Zahlen sind 0 und 1: var fibonacci = function (n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); }; for (var i = 0; i <= 10; i += 1) { document.writeln('// ' + i + ': ' + fibonacci(i)); } // // // // // // // // // // //
0: 0 1: 1 2: 1 3: 2 4: 3 5: 5 6: 8 7: 13 8: 21 9: 34 10: 55
Das funktioniert, macht sich aber viel unnötige Arbeit. Die fibonacci-Funktion wird 453-mal aufgerufen. Wir rufen sie 11-mal auf, und sie selbst ruft sich 442-mal auf, um Werte zu berechnen, die sehr wahrscheinlich schon berechnet wurden. Wenn wir die Funktion »memoizieren«, können wir das Arbeitspensum deutlich reduzieren. Wir halten unsere Ergebnisse in einem memo-Array fest, das wir in einer Closure verstecken können. Wird unsere Funktion aufgerufen, schaut sie zuerst nach, ob sie das Ergebnis bereits kennt. Ist das der Fall, kann sie direkt mit dem Ergebnis zurückkehren: var fibonacci = function ( ) { var memo = [0, 1]; var fib = function (n) { var result = memo[n]; if (typeof result !== 'number') { result = fib(n - 1) + fib(n - 2); memo[n] = result; } return result; }; return fib; }( );
Max. Linie
Diese Funktion gibt das gleiche Ergebnis zurück, wird aber nur 29-mal aufgerufen. Wir rufen sie 11-mal auf. Sie ruft sich selbst 18-mal auf, um die vorher festgehaltenen Ergebnisse abzurufen.
Memoization | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
49
Max. Linie
Links Wir können das verallgemeinern, indem wir eine Funktion entwickeln, die uns dabei hilft, memoisierte Funktionen zu erzeugen. Die memoizer-Funktion besitzt ein memo-Array und eine fundamental-Funktion. Sie gibt eine shell-Funktion zurück, die den memo-Speicher verwaltet und die fundamental-Funktion ganz nach Bedarf aufruft. Wir übergeben die shell-Funktion und die Parameter der Funktion an die fundamental-Funktion: var memoizer = function (memo, fundamental) { var shell = function (n) { var result = memo[n]; if (typeof result !== 'number') { result = fundamental(shell, n); memo[n] = result; } return result; }; return shell; };
Wir können fibonacci nun mit dem Memoizer definieren, indem wir ein memo-Array und eine fundamental-Funktion bereitstellen: var fibonacci = memoizer([0, 1], function (shell, n) { return shell(n - 1) + shell(n - 2); });
Indem wir Funktionen entwickeln, die andere Funktionen erzeugen, können wir die zu erledigende Arbeit deutlich reduzieren. Um beispielsweise eine memo-fähige Fakultätsfunktion zu entwickeln, müssen wir nur die grundlegende Fakultätsformel übergeben: var factorial = memoizer([1, 1], function (shell, n) { return n * shell(n - 1); });
Max. Linie
Max. Linie 50
|
Kapitel 4: Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 5
KAPITEL 5
Vererbung
Divides one thing entire to many objects; Like perspectives, which rightly gazed upon Show nothing but confusion… – William Shakespeare, The Tragedy of King Richard the Second
Vererbung ist in den meisten Programmiersprachen ein wichtiges Thema. Bei Sprachen, die Klassen verwenden (wie Java) bietet die Vererbung zwei nützliche Dienste. Zuerst einmal stellt sie eine Form der Wiederverwendung von Code dar. Ähnelt eine neue Klasse in großen Teilen einer schon existierenden Klasse, müssen Sie nur die Unterschiede spezifizieren. Code-Wiederverwendungsmuster sind extrem wichtig, da sie das Potenzial haben, die Kosten der Softwareentwicklung deutlich zu reduzieren. Der andere Vorteil der Vererbung über Klassen besteht darin, dass sie die Spezifikation eines Typsystems einschließt. Das bewahrt den Programmierer in den meisten Fällen davor, explizit Casting-Operationen durchführen zu müssen, was eine sehr gute Sache ist, da die Sicherheit eines Typsystems beim Casting verloren geht. JavaScript führt als schwach typisierte Sprache kein Casting durch. Die Abstammung eines Objekts ist irrelevant. Das Einzige, was an einem Objekt interessiert, ist, was es kann, nicht, von wem es abstammt. JavaScript bietet einen wesentlich größeren Satz von Code-Wiederverwendungsmustern an. Es kann die klassenorientierten Muster nachahmen, unterstützt aber auch andere Muster, die wesentlich ausdrucksstärker sind. Der Satz möglicher Vererbungsmuster ist in JavaScript sehr groß. In diesem Kapitel sehen wir uns einige der wichtigsten Muster an. Wesentlich kompliziertere Konstruktionen sind möglich, aber üblicherweise ist es besser, die Dinge einfach zu halten.
Max. Linie
Bei Sprachen, die mit Klassen arbeiten, sind Objekte Instanzen von Klassen, und eine Klasse kann von einer anderen Klasse erben. JavaScript ist eine prototypische Sprache, d.h., Objekte können direkt von anderen Objekten erben.
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
51
Max. Linie
Links Pseudoklassische Vererbung JavaScript wird wegen seiner prototypischen Natur als zwiespältig angesehen. Der Prototypmechanismus wird durch einige komplizierte syntaktische Konstrukte verschleiert, die vage an die Syntax mit Klassen erinnern. Anstatt Objekte direkt von anderen Objekten erben zu lassen, gibt es einen unnötigen Umweg in Form von Konstruktorfunktionen. Wird ein Funktionsobjekt angelegt, führt der das Funktionsobjekt erzeugende Function-Konstruktor Code wie diesen aus: this.prototype = {constructor: this};
Das neue Funktionsobjekt erhält eine prototype-Eigenschaft, deren Wert ein Objekt mit einer constructor-Eigenschaft ist, deren Wert wiederum das neue Funktionsobjekt ist. Das prototype-Objekt ist der Ort, an dem ererbte Elemente abzulegen sind. Jede Funktion erhält ein prototype-Objekt, weil die Sprache keine Möglichkeit bietet herauszufinden, welche Funktionen als Konstruktoren verwendet werden sollen. Die constructor-Eigenschaft ist nicht nützlich. Wichtig ist das prototype-Objekt. Wird eine Funktion über das Muster für den Aufruf von Funktionen mit new aufgerufen, verändert das die Art und Weise, wie die Funktion ausgeführt wird. Wäre der new-Operator eine Methode (und kein Operator), könnte er wie folgt implementiert werden: Function.method('new', function ( ) { // Neues Objekt erzeugen, das vom // Konstruktor-Prototyp erbt. var that = Object.beget(this.prototype); // Konstruktor aufrufen. –this- an das // neue Objekt binden. var other = this.apply(that, arguments); // Ist dessen Rückgabewert kein Objekt, // das neue Objekt ersetzen. return (typeof other === 'object' && other) || that; });
Wir können einen Konstruktor definieren und dessen prototype erweitern: var Mammal = function (name) { this.name = name; };
Max. Linie
Mammal.prototype.get_name = function ( ) {
52
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts return this.name; }; Mammal.prototype.says = function ( ) { return this.saying || ''; };
Nun können wir eine Instanz erzeugen: var myMammal = new Mammal('Herb the Mammal'); var name = myMammal.get_name( ); // 'Herb the Mammal'
Wir können eine weitere Pseudoklasse erzeugen, die von Mammal erbt, indem wir dessen constructor-Funktion definieren und dessen prototype durch eine Instanz von Mammal ersetzen: var Cat = function (name) { this.name = name; this.saying = 'meow'; }; // Cat.prototype durch neue Instanz von Mammal ersetzen Cat.prototype = new Mammal( ); // Neuen Prototyp um die Methoden // purr und get_name erweitern. Cat.prototype.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; Cat.prototype.get_name = function ( ) { return this.says( ) + ' ' + this.name + ' ' + this.says( ); }; var var var var //
Max. Linie
myCat = new Cat('Henrietta'); says = myCat.says( ); // 'meow' purr = myCat.purr(5); // 'r-r-r-r-r' name = myCat.get_name( ); 'meow Henrietta meow'
Das Pseudoklassen-Muster sollte irgendwie objektorientiert aussehen, wirkt stattdessen aber recht fremd. Wir können das ein wenig entschärfen, indem wir eine method-Methode verwenden und eine inherits-Methode definieren:
Pseudoklassische Vererbung This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
53
Max. Linie
Links Function.method('inherits', function (Parent) { this.prototype = new Parent( ); return this; });
Unsere inherits- und method-Methoden geben this zurück, was uns die Programmierung im Kaskaden-Stil erlaubt. Unser Cat können wir nun in einer Anweisung erzeugen: var Cat = function (name) { this.name = name; this.saying = 'meow'; }. inherits(Mammal). method('purr', function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }). method('get_name', function ( ) { return this.says( ) + ' ' + this.name + ' ' + this.says( ); });
In dem wir das prototype-Zeug verstecken, sieht das Ganze nicht mehr so fremd aus. Aber haben wir wirklich etwas verbessert? Wir besitzen nun Konstruktorfunktionen, die sich wie Klassen verhalten, aber dieser Ansatz kann auch zu überraschenden Verhaltensweisen führen. Es gibt keine Privatsphäre, d.h., alle Eigenschaften sind öffentlich, und es gibt keinen Zugriff auf super-Methoden. Noch schlimmer ist, dass es bei der Verwendung von Konstruktorfunktionen ein echtes Risiko gibt. Wenn Sie vergessen, beim Aufruf einer Konstruktorfunktion das new-Präfix anzugeben, dann wird this nicht an ein neues Objekt gebunden. Leider wird this an das globale Objekt gebunden, so dass Sie nicht Ihr neues Objekt erweitern, sondern mit globalen Variablen arbeiten. Das ist wirklich übel. Es gibt keine Warnung vom Compiler, und auch zur Laufzeit erscheint kein Warnhinweis.
Max. Linie
Dies ist ein ernster Designfehler der Sprache. Um diesem Problem entgegenzuwirken, gibt es die Konvention, alle Konstruktorfunktionen mit einem großgeschriebenen Anfangsbuchstaben zu beginnen, und nichts anderes mit einem großen Anfangsbuchstaben zu schreiben. Das gibt uns die Hoffnung, dass eine visuelle Untersuchung ein fehlendes new aufspürt. Eine wesentlich bessere Alternative besteht darin, new überhaupt nicht zu verwenden.
54
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Das Arbeiten mit Pseudoklassen kann Programmierern Sicherheit geben, die mit JavaScript nicht vertraut sind, aber sie verschleiert auch die wahre Natur der Sprache. Die durch Klassen inspirierte Notation kann Programmierer dazu verleiten, unnötig tiefe und komplizierte Hierarchien aufzubauen. Ein Großteil der Komplexität von Klassenhierarchien ist durch die Beschränkungen statischer Typ-Prüfungen motiviert. JavaScript ist von diesen Beschränkungen völlig frei. Bei Sprachen, die Klassen verwenden, ist die Vererbung die einzige Form der Wiederverwendung von Code. JavaScript besitzt mehr und bessere Optionen.
Objekt-Specifier Es kommt vor, dass einem Konstruktor eine große Zahl von Parametern übergeben wird. Das kann recht mühsam sein, weil es schwierig sein kann, sich die Reihenfolge der Argumente zu merken. In solchen Fällen wäre es wesentlich schöner, wenn wir den Konstruktor so entwickeln, dass er einen einzelnen Objekt-Specifier akzeptiert. Dieses Objekt enthält die Spezifikation des zu konstruierenden Objekts. Anstelle von var myObject = maker(f, l, m, c, s);
können wir dann Folgendes schreiben: var myObject = maker({ first: f, last: l, state: s, city: c });
Die Argumente können nun in beliebiger Reihenfolge angegeben werden. Argumente können ausgelassen werden, wenn der Konstruktor Standardwerte clever einsetzt, und der Code ist wesentlich einfacher zu lesen. Das kann ein zusätzlicher Vorteil sein, wenn Sie mit JSON (siehe Anhang A) arbeiten. JSON-Text kann nur Daten beschreiben, aber manchmal stellen diese Daten ein Objekt dar, und es wäre nützlich, diese Daten mit ihren Methoden zu assoziieren. Das ist sehr einfach möglich, wenn der Konstruktor einen Objekt-Specifier verwendet, da man einfach das JSON-Objekt an den Konstruktor übergeben kann und dieser ein vollständig aufgebautes Objekt zurückgibt.
Prototypische Vererbung Max. Linie
Bei einem rein prototypischen Muster verzichten wir völlig auf Klassen. Stattdessen konzentrieren wir uns auf die Objekte. Prototypische Vererbung ist konzeptionell einfacher als die Vererbung über Klassen: Ein neues Objekt kann die Eigenschaften
Prototypische Vererbung This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
55
Max. Linie
Links eines alten Objekts erben. Das ist einem vielleicht nicht vertraut, aber doch sehr einfach zu verstehen. Sie beginnen damit, irgendein nützliches Objekt aufzubauen. Sie können dann viele weitere Objekte erzeugen, die diesem ähneln. Der Klassifizierungsprozess, bei dem eine Anwendung in eine Reihe verschachtelter abstrakter Klassen aufgeteilt wird, kann vollständig vermieden werden. Fangen wir mit einem Objektliteral an, um ein nützliches Objekt zu erzeugen: var myMammal = { name : 'Herb the Mammal', get_name : function ( ) { return this.name; }, says : function ( ) { return this.saying || ''; } };
Sobald wir ein sinnvolles Objekt besitzen, können wir mit der Object.create-Methode aus Kapitel 3 weitere Objekte erzeugen. Die neuen Instanzen passen wir dann an: var myCat = Object.create(myMammal); myCat.name = 'Henrietta'; myCat.saying = 'meow'; myCat.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; myCat.get_name = function ( ) { return this.says ( ) + ' ' + this.name + ' ' + this.says ( ); };
Das nennt sich differenzielle Vererbung. Durch die Anpassung des neuen Objekts geben wir die Unterschiede zu dem Objekt an, auf dem es basiert.
Max. Linie
Manchmal ist es für die Datenstrukturen sinnvoll, von anderen Datenstrukturen zu erben. Hier ein Beispiel: Nehmen wir an, wir parsen eine Sprache wie JavaScript oder TEX, bei der ein Paar geschweifter Klammern einen Geltungsbereich anzeigt. Innerhalb eines Geltungsbereichs definierte Elemente sind außerhalb des Geltungsbereichs nicht sichtbar. In gewissem Sinne erbt ein innerer Geltungsbereich von seinem äußeren Geltungsbereich. JavaScript-Objekte können diese Beziehung sehr gut darstellen. Die block-Funktion wird aufgerufen, wenn eine linke geschweifte Klammer erkannt wird. Die parse-Funktion sucht Symbole in scope und erweitert scope, wenn sie neue Symbole definiert:
56
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts var block = function ( ) { // Aktuellen Geltungsbereich merken. Neuen Geltungsbereich // erzeugen, der alles aus dem aktuellen Geltungsbereich enthält. var oldScope = scope; scope = Object.create(scope); // Über die linke geschweifte Klammer hinaus vorrücken. advance('{'); // Parsing mit neuem Geltungsbereich. parse(scope); // Hinter die rechte geschweifte Klammer vorrücken, neuen // Geltungsbereich verwerfen und den alten wiederherstellen. advance('}'); scope = oldScope; };
Funktionale Vererbung Eine Schwäche der bisher vorgestellten Vererbungsmuster ist, dass wir keine Privatsphäre besitzen. Alle Eigenschaften eines Objekts sind sichtbar. Wir besitzen keine privaten Variablen und keine privaten Methoden. Manchmal spielt das keine Rolle, manchmal hingegen ist es von wesentlicher Bedeutung. Aus Frustration täuschen einige weniger erfahrene Programmierer Privatsphäre vor. Soll eine Eigenschaft privat sein, geben sie ihr einen seltsam aussehenden Namen und hoffen darauf, dass die anderen Benutzer, die den Code verwenden, so tun, als ob sie die seltsam aussehenden Member nicht sehen könnten. Glücklicherweise gibt es mit dem ModulMuster eine wesentlich bessere Alternative. Wir beginnen damit, eine Funktion zu erzeugen, die Objekte produziert. Wir geben ihr einen Namen, der mit einem Kleinbuchstaben beginnt, weil sie das new-Präfix nicht verwenden wird. Die Funktion umfasst vier Schritte: 1. Sie erzeugt ein neues Objekt. Es gibt sehr viele Möglichkeiten, um ein Objekt zu erzeugen. Sie kann ein Objektliteral generieren oder eine Konstruktorfunktion mit einem new-Präfix aufrufen, sie kann die Object.create-Methode verwenden, um eine neue Instanz eines existierenden Objekts zu erzeugen, oder sie kann jede Funktion aufrufen, die ein Objekt zurückliefert.
Max. Linie
2. Sie definiert optional private Instanzvariablen und Methoden. Diese sind einfach gewöhnliche vars der Funktion. 3. Sie erweitert das neue Objekt um Methoden. Diese Methoden besitzen privilegierten Zugriff auf die im zweiten Schritt definierten Parameter und vars.
Funktionale Vererbung This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
57
Max. Linie
Links 4. Sie gibt das neue Objekt zurück. Hier eine Pseudocode-Vorlage für einen funktionalen Konstruktor (der Fettdruck dient der Hervorhebung): var constructor = function (spec, my) { var that, weitere private Instanzvariablen; my = my || {}; my um gemeinsam genutzte Variablen und Funktionen erweitern that = ein neues Objekt; that um privilegierte Methoden erweitern return that; };
Das spec-Objekt enthält alle Informationen, die der Konstruktor benötigt, um eine Instanz zu erzeugen. Der Inhalt von spec könnte in private Variablen kopiert oder von anderen Funktionen transformiert werden. Oder die Methoden können ganz nach Bedarf auf Informationen aus spec zugreifen. (Eine Vereinfachung besteht darin, spec durch einen einzelnen Wert zu ersetzen. Das ist nützlich, wenn das zu konstruierende Objekt kein ganzes spec-Objekt benötigt.) Das my-Objekt dient als Container für geheime Werte, die von den Konstruktoren der Vererbungskette verwendet werden. Die Verwendung des my-Objekts ist optional. Wird kein my-Objekt übergeben, dann wird eines erzeugt. Als Nächstes deklarieren Sie die privaten Instanzvariablen und privaten Methoden des Objekts. Das geschieht einfach durch Deklaration von Variablen. Die Variablen und inneren Funktionen des Konstruktors werden die privaten Member der Instanz. Die inneren Funktionen haben Zugriff auf spec, my, that und die privaten Variablen. Als Nächstes fügen Sie die geheimzuhaltenden Werte in das my-Objekt ein. Das geschieht mithilfe von Zuweisungen: my.member = wert;
Max. Linie
Nun erzeugen wir ein neues Objekt und weisen es an that zu. Es gibt viele Möglichkeiten, ein neues Objekt zu erzeugen. Wir können ein Objektliteral verwenden. Wir können einen pseudoklassenorientierten Konstruktor mit dem Operator new aufrufen. Wir können die Methode Object.create auf ein Prototyp-Objekt anwenden. Oder wir rufen einen weiteren funktionalen Konstruktor auf, dem wir ein spec-Objekt (möglicherweise das gleiche spec-Objekt, das an diesen Konstruktor übergeben wurde) und das my-Objekt übergeben. Das my-Objekt erlaubt es dem anderen Konstruktor, das Material zu verwenden, das wir in my abgelegt haben. Dieser andere Konstruktor kann auch eigene geheime Werte in my ablegen, so dass unser Konstruktor sie ebenfalls nutzen kann.
58
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Als Nächstes erweitern wir that um die privilegierten Methoden, aus denen die Objektschnittstelle besteht. Wir können neue Funktionen an Member von that zuweisen. Oder (was sicherer ist) wir definieren die Funktionen zuerst als private Methoden und weisen diese dann an that zu: var methodical = function ( ) { ... }; that.methodical = methodical;
Der Vorteil der zweistufigen Definition von methodical besteht darin, dass andere Methoden, die methodical aufrufen wollen, methodical( ) anstelle von that. methodical( ) verwenden können. Wurde an der Instanz herumgespielt oder ist sie beschädigt, so dass that.methodical ersetzt wird, dann arbeiten methodical aufrufende Methoden auch weiterhin, weil deren privates methodical von der Modifikation der Instanz nicht betroffen ist. Schließlich geben wir that zurück. Wenden wir das auf unser mammal-Beispiel an. Wir benötigen hier kein my, weshalb wir es hier weglassen, aber wir verwenden das spec-Objekt. Die Eigenschaften name und saying sind nun vollständig privat. Sie sind nur über die privilegierten get_name- und says-Methoden zugänglich: var mammal = function (spec) { var that = {}; that.get_name = function ( ) { return spec.name; }; that.says = function ( ) { return spec.saying || ''; }; return that; }; var myMammal = mammal({name: 'Herb'});
Beim pseudoklassenorientierten Muster musste die Cat-Konstruktorfunktion Arbeiten wiederholen, die bereits vom Mammal-Konstruktor erledigt worden waren. Das ist beim funktionalen Muster nicht notwendig, weil der Cat-Konstruktor den MammalKonstruktor aufruft und so Mammal den Großteil der Objekterzeugung erledigen lässt. Cat muss sich nur um die Unterschiede kümmern:
Max. Linie
var cat = function (spec) { spec.saying = spec.saying || 'meow'; var that = mammal(spec); that.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) {
Funktionale Vererbung This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
59
Links if (s) { s += '-'; } s += 'r'; } return s; }; that.get_name = function ( ) { return that.says( ) + ' ' + spec.name + ' ' + that.says( ); return that; }; var myCat = cat({name: 'Henrietta'});
Das funktionale Muster bietet uns auch eine Möglichkeit, mit Supermethoden umzugehen. Wir bauen eine superior-Methode auf, die einen Methodennamen verlangt und eine Funktion zurückliefert, die diese Methode aufruft. Die Funktion ruft die Originalmethode selbst dann auf, wenn die Eigenschaft sich ändert: Object.method('superior', function (name) { var that = this, method = that[name]; return function ( ) { return method.apply(that, arguments); }; });
Lassen Sie uns das an coolcat ausprobieren, die sich genau wie cat verhält, aber eine coolere get_name-Methode verwendet, die die Supermethode aufruft. Das verlangt nur ein kleines bisschen Vorbereitung. Wir deklarieren eine super_get_nameVariable und weisen ihr das Ergebnis der Aufrufs der superior-Methode zu: var coolcat = function (spec) { var that = cat(spec), super_get_name = that.superior('get_name'); that.get_name = function (n) { return 'like ' + super_get_name( ) + ' baby'; }; return that; }; var myCoolCat = coolcat({name: 'Bix'}); var name = myCoolCat.get_name( ); // 'like meow Bix meow baby'
Das funktionale Muster bietet ein großes Maß an Flexibilität. Es verlangt weniger Aufwand als das pseudoklassenorientierte Muster und bietet uns eine bessere Kapselung, ein besseres Information Hiding und Zugriff auf Super-Methoden.
Max. Linie
Max. Linie 60
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Ist der gesamte Zustand eines Objekts privat, ist es vor unerwünschten Änderungen geschützt. Eigenschaften des Objekts können ersetzt oder gelöscht werden, aber die Integrität des Objekts wird nicht kompromittiert. Erzeugen wir ein Objekt im funktionalen Stil und nutzen alle Methoden des Objekts weder this noch that, dann ist das Objekt dauerhaft (durabel). Ein beständiges Objekt ist einfach eine Sammlung von Funktionen, die wie Fähigkeiten agieren. Ein beständiges Objekt kann nicht kompromittiert werden. Der Zugriff auf ein dauerhaftes Objekt gibt einem Angreifer nicht die Möglichkeit, auf den internen Zustand des Objekts zuzugreifen, außer in dem Rahmen, den die Methoden erlauben.
Teile Wir bauen Objekte aus Gruppen von Teilen zusammen. Zum Beispiel können wir eine Funktion entwickeln, die jedes Objekt um einfache Eventhandling-Features erweitert. Sie fügt eine on-Methode, eine fire-Methode und eine private Event-Registrierung hinzu: var eventuality = function (that) { var registry = {}; that.fire = function (event) { // // // // //
Event auf ein Objekt „abfeuern“. Das Event kann entweder ein String mit dem Namen des Events sein oder ein Objekt mit einer type-Eigenschaft, die den Namen des Events enthält. Durch die ’on’-Methode registrierte Handler, deren Namen dem Event-Namen entsprechen, werden aufgerufen. var array, func, handler, i, type = typeof event === 'string' ? event : event.type;
// Existiert ein Array von Handlern für dieses Event, dann wird es // durchlaufen und alle Handler werden nacheinander ausgeführt. if (registry.hasOwnProperty(type)) { array = registry[type]; for (i = 0; i < array.length; i += 1) { handler = array[i]; // Ein Handler-Datensatz enthält eine Methode und ein optionales // Array von Parametern. Ist die Methode ein Name, erfolgt ein // Lookup der Funktion.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Teile
|
61
Links func = handler.method; if (typeof func === 'string') { func = this[func]; } // Einen Handler aufrufen. Enthielt das Record Parameter, // dann übergeben wir sie. Anderenfalls übergeben wir das // Event-Objekt. func.apply(this, handler.parameters || [event]); } } return this; }; that.on = function (type, method, parameters) { // Ein Event registrieren. Handler-Record erzeugen und in // einem Handler-Array ablegen. Falls es für diesen Typ // noch keines gibt, legen wir das Array an. var handler = { method: method, parameters: parameters }; if (registry.hasOwnProperty(type)) { registry[type].push(handler); } else { registry[type] = [handler]; } return this; }; return that; };
Wir könnten eventuality für jedes individuelle Objekt aufrufen und es mit Eventhandling-Methoden versehen. Wir könnten es auch in einer Konstruktorfunktion aufrufen, bevor that zurückgegeben wird: eventuality(that);
Auf diese Weise kann ein Konstruktor Objekte aus einer Gruppe von Teilen zusammensetzen. Die schwache Typisierung von JavaScript ist hier ein großer Vorteil, weil wir nicht durch ein Typsystem behindert werden, das sich an der Abstammung von Klassen orientiert. Stattdessen konzentrieren wird uns auf den Charakter des Inhalts. Soll eventuality Zugriff auf den privaten Zustand des Objekts haben, können wir ihr das my-Paket übergeben.
Max. Linie
Max. Linie 62
|
Kapitel 5: Vererbung
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 6
KAPITEL 6
Arrays
Thee I’ll chase hence, thou wolf in sheep’s array. – William Shakespeare, The First Part of Henry the Sixth
Ein Array ist ein linearer Speicherbereich, bei dem auf Elemente über Integerwerte zugegriffen wird, die zur Berechnung von Offsets verwendet werden. Arrays können sehr schnelle Datenstrukturen sein. Leider besitzt JavaScript eine solche Art von Array nicht. Stattdessen besitzt JavaScript ein Objekt mit Array-typischen Charakteristika. Es wandelt Array-Indizes in Strings um, die dann verwendet werden, um Eigenschaften zu erzeugen. Es ist deutlich langsamer als ein echtes Array, kann aber bequemer zu nutzen sein. Abruf und Aktualisierung von Eigenschaften funktionieren genau wie bei Objekten, mit der Ausnahme eines speziellen Tricks für Integer-Eigenschaftsnamen. Arrays haben ein eigenes literales Format. Arrays besitzen darüber hinaus einen wesentlich nützlicheren Satz fest eingebauter Methoden, die in Kapitel 8 beschrieben werden.
Array-Literale Array-Literale stellen eine sehr praktische Notation zur Erzeugung neuer ArrayWerte dar. Ein Array-Literal besteht aus einem Paar eckiger Klammern, die einen oder mehrere durch Kommata getrennte Werte umschließen. Ein Array-Literal kann überall da stehen, wo ein Ausdruck stehen kann. Der erste Wert erhält den Eigenschaftsnamen '0', der zweite Wert den Eigenschaftsnamen '1' und so weiter: var empty = var numbers 'zero', 'five', ];
[]; = [ 'one', 'two', 'three', 'four', 'six', 'seven', 'eight', 'nine'
Max. Linie
Max. Linie | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
63
Links empty[1] numbers[1]
// undefined // 'one'
empty.length numbers.length
// 0 // 10
Das Objektliteral var numbers_object = { '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine' };
erzeugt ein vergleichbares Ergebnis. Sowohl numbers als auch numbers_object sind Objekte, die 10 Eigenschaften enthalten, und diese Eigenschaften enthalten genau die gleichen Namen und Werte. Dennoch gibt es deutliche Unterschiede. numbers erbt von Array.prototype, während numbers_object von Object.prototype erbt, so dass numbers über einen größeren Satz nützlicher Methoden verfügt. Außerdem besitzt numbers die mysteriöse length-Eigenschaft, numbers_object hingegen nicht. In den meisten Sprachen müssen die Elemente eines Arrays alle vom gleichen Typ sein. In JavaScript darf ein Array hingegen eine Mixtur von Werten enthalten: var misc = [ 'string', 98.6, true, false, null, undefined, ['nested', 'array'], {object: true}, NaN, Infinity ]; misc.length // 10
Length Jedes Array besitzt eine length-Eigenschaft. Im Gegensatz zu den meisten anderen Sprachen besitzt die Array-Länge bei JavaScript keine obere Grenze. Speichern Sie ein Element mit einem Index, der größer oder gleich der aktuellen Länge ist, dann erhöht sich length um das neue Element. Es gibt also keine Fehler in Bezug auf die Array-Grenze. Die length-Eigenschaft entspricht dem größten Integer-Eigenschaftsnamen im Array plus eins. Das ist nicht notwendigerweise identisch mit der Anzahl von Eigenschaften in diesem Array: var myArray = []; myArray.length
// 0
myArray[1000000] = true; myArray.length // 1000001 // myArray enthält eine Eigenschaft.
Max. Linie 64
|
Kapitel 6: Arrays
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Der Postfix-Index [] wandelt seinen Ausdruck über die toString-Methode des Ausdrucks (wenn er eine besitzt) in einen String um. Dieser String wird dann als Eigenschaftsname verwendet. Sieht der String wie ein positiver Integerwert aus, der größer oder gleich der aktuellen Länge des Arrays und kleiner als 4.294.967.295 ist, dann wird die Länge des Arrays auf den Wert des neuen Index plus eins gesetzt. Die Länge kann explizit gesetzt werden. Machen Sie length größer, wird nicht mehr Platz für das Array alloziert. Verkleinern Sie length, werden alle Eigenschaften mit einem Index größer oder gleich der neuen Länge gelöscht: numbers.length = 3; // numbers ist ['zero', 'one', 'two']
Ein neues Element kann an das Ende des Arrays angehängt werden, indem man es dem aktuellen length-Wert des Arrays zuweist: numbers[numbers.length] = 'shi'; // numbers ist ['zero', 'one', 'two', 'shi']
Manchmal ist es bequemer, die push-Methode zu verwenden, um das gleiche Ziel zu erreichen: numbers.push('go'); // numbers ist ['zero', 'one', 'two', 'shi', 'go']
Delete Da Arrays in JavaScript eigentlich Objekte sind, kann der delete-Operator verwendet werden, um Elemente aus einem Array zu entfernen: delete numbers[2]; // numbers ist ['zero', 'one', undefined, 'shi', 'go']
Leider hinterlässt das eine Lücke im Array. Das liegt daran, dass die Elemente rechts neben dem gelöschten Element ihre ursprünglichen Namen behalten. Üblicherweise wollen Sie aber die Namen aller rechts stehenden Elemente entsprechend dekrementieren. Glücklicherweise kennen JavaScript-Arrays eine splice-Methode. Mit dieser können Sie chirurgische Eingriffe an einem Array vornehmen, d.h., eine Reihe von Elementen löschen und durch andere Elemente ersetzen. Das erste Argument ist ein Index auf das Array. Das zweite Argument ist die Zahl der zu löschenden Elemente. Alle zusätzlichen Argumente werden an dieser Stelle im Array eingefügt: numbers.splice(2, 1); // numbers ist ['zero', 'one', 'shi', 'go']
Max. Linie
Bei der Eigenschaft mit dem Wert 'shi' wird der Schlüssel von '4' zu '3'. Weil jede Eigenschaft hinter der gelöschten Eigenschaft gelöscht und mit einem neuen Schlüssel erneut eingefügt werden muss, dauert das bei langen Arrays so seine Zeit.
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Delete
|
65
Max. Linie
Links Aufzählung Da die Arrays von JavaScript in Wirklichkeit Objekte sind, kann die for...inAnweisung verwendet werden, um alle Eigenschaften eines Arrays durchzugehen. Leider gibt for...in keinerlei Garantien zur Reihenfolge der Eigenschaften, und die meisten Array-Anwendungen erwarten, dass die Elemente eine numerische Reihenfolge aufweisen. Außerdem gibt es immer noch das Problem mit unerwartet aus der Prototype-Kette auftauchenden Eigenschaften. Glücklicherweise vermeidet die konventionelle for-Anweisung diese Probleme. Die for-Anweisung von JavaScript entspricht denen der meisten an C angelehnten Sprachen. Sie wird über drei Klauseln gesteuert: Die erste initialisiert die Schleife, die zweite bildet die while-Bedingung und die dritte das Inkrement: var i; for (i = 0; i < myArray.length; i += 1) { document.writeln(myArray[i]); }
Verwirrung Ein typischer Fehler in JavaScript-Programmen ist die Verwendung eines Objekts, wenn ein Array verlangt wird, oder die Verwendung eines Arrays, wenn ein Objekt benötigt wird. Die Regel ist einfach: Wenn die Eigenschaftsnamen kleine sequenzielle Integerwerte sind, sollten Sie ein Array verwenden. Anderenfalls verwenden Sie ein Objekt. JavaScript ist bei der Unterscheidung zwischen Arrays und Objekten selbst nicht klar. Der typeof-Operator meldet den Typ eines Arrays als 'object', was nicht gerade hilfreich ist. JavaScript besitzt keinen guten Mechanismus zur Unterscheidung zwischen Arrays und Objekten. Wir können dieses Defizit ausgleichen, indem wir unsere eigene is_ array-Funktion definieren: var is_array = function (value) { return value && typeof value === 'object' && value.constructor === Array; };
Leider funktioniert das nicht für Arrays, die in einem anderen Fenster oder Frame definiert wurden. Sollen auch diese korrekt erkannt werden, müssen Sie etwas mehr tun: var is_array = function (value) { return value && typeof value === 'object' &&
Max. Linie 66
|
Kapitel 6: Arrays
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts typeof value.length === 'number' && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length')); };
Zuerst prüfen wir, ob der Wert wahr ist. Wir machen das, um null und andere falsche Objekte auszusortieren. Danach prüfen wir, ob der typeof-Wert 'object' ist. Das trifft auf Objekte, Arrays und (seltsamerweise) auf null zu. Nun prüfen wir, ob der Wert eine length-Eigenschaft besitzt, die eine Zahl ist. Das gilt für Arrays eigentlich immer, üblicherweise aber nicht für Objekte. Nun klären wir, ob der Wert eine splice-Methode kennt. Das gilt erneut für alle Arrays. Schließlich fragen wir, ob die length-Eigenschaft aufzählbar ist (wird length bei einer for...inSchleife erzeugt?). Das trifft auf Arrays nicht zu. Das ist der zuverlässigste Test, den ich für Arrays finden konnte. Leider ist er etwas kompliziert. Mit einem solchen Test ist es möglich, Funktionen zu schreiben, die eine Sache tun, wenn man ihnen einen Wert übergibt, und viele Dinge, wenn man ihnen ein Array von Werten übergibt.
Methoden JavaScript stellt für die Arbeit mit Arrays eine Reihe von Methoden zur Verfügung. Diese Methoden sind in Array.prototype gespeicherte Funktionen. In Kapitel 3 haben Sie gesehen, dass Object.prototype erweitert werden kann. Array.prototype kann ebenfalls erweitert werden. Nehmen wir zum Beispiel an, wir wollen eine Array-Methode hinzufügen, die Berechnungen über ein Array erlaubt: Array.method('reduce', function (f, value) { var i; for (i = 0; i < this.length; i += 1) { value = f(this[i], value); } return value; });
Indem wir eine Funktion zu Array.prototype hinzufügen, erbt jedes Array diese Methode. In diesem Fall haben wir eine reduce-Methode definiert, die eine Funktion und einen Startwert akzeptiert. Für jedes Element des Arrays wird die Funktion mit diesem Element und dem Wert aufgerufen und ein neuer Wert berechnet. Sobald sie fertig ist, gibt sie den Wert zurück. Wenn wir eine Funktion übergeben, die zwei Zahlen addiert, berechnet sie die Summe. Übergeben wir eine Funktion, die zwei Zahlen multipliziert, wird das Produkt berechnet: // Array mit Zahlen anlegen.
Max. Linie
Max. Linie
var data = [4, 8, 15, 16, 23, 42];
Methoden This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
67
Links // Zwei einfache Funktionen definieren. Eine addiert // zwei Zahlen, die andere multipliziert zwei Zahlen. var add = function (a, b) { return a + b; }; var mult = function (a, b) { return a * b; }; // reduce-Methode für die Daten aufrufen und // die add-Funktion übergeben. var sum = data.reduce(add, 0);
// sum ist 108
// reduce-Methode erneut aufrufen und diesmal // die mult-Funktion übergeben. var product = data.reduce(mult, 1); // product ist 7418880
Da ein Array eigentlich ein Objekt ist, können wir Methoden direkt einzelnen Arrays zuordnen: // data-Array um eine total-Funktion erweitern. data.total = function ( ) { return this.reduce(add, 0); }; total = data.total( );
// total ist 108
Da der String 'total' kein Integerwert ist, verändert das Hinzufügen der totalEigenschaft nicht die Länge des Arrays. Arrays sind am nützlichsten, wenn die Eigenschaftsnamen Integerwerte sind, aber sie bleiben doch Objekte, und Objekte akzeptieren jeden String als Eigenschaftsnamen. Die Verwendung der Object.create-Methode aus Kapitel 3 ist bei Arrays nicht sinnvoll, weil sie ein Objekt erzeugt und kein Array. Das erzeugte Objekt erbt die Werte und Methoden des Arrays, besitzt aber nicht die Spezial-Eigenschaft length.
Dimensionen
Max. Linie
JavaScript-Arrays werden üblicherweise nicht initialisiert. Fordern Sie ein neues Array mit [] an, dann ist es leer. Greifen Sie auf ein fehlendes Element zu, erhalten Sie undefined zurück. Sind Sie sich dessen bewusst oder weisen Sie jedem Element einen Wert zu, bevor sie es nutzen, dann ist alles in Ordnung. Doch wenn Sie Algorithmen implementieren, die davon ausgehen, dass jedes Element mit einem be-
68
|
Kapitel 6: Arrays
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts kannten Wert (wie 0) beginnt, dann müssen Sie das Array selbst vorbereiten. JavaScript hätte eine Methode wie Array.dim zur Verfügung stellen sollen, um das zu erledigen, aber wir können diesen Mangel leicht beheben: Array.dim = function (dimension, initial) { var a = [], i; for (i = 0; i < dimension; i += 1) { a[i] = initial; } return a; }; // Erzeuge ein Array mit 10 Nullen. var myArray = Array.dim(10, 0);
JavaScript kennt keine Arrays mit mehr als einer Dimension, aber wie die meisten an C angelehnten Sprachen können Sie Arrays von Arrays erzeugen: var matrix = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]; matrix[2][1]
// 7
Um ein zweidimensionales Array (oder ein Array von Arrays) anzulegen, müssen Sie die Arrays selbst aufbauen: for (i = 0; i < n; i += 1) { my_array[i] = []; } // Hinweis: Array.dim(n, []) funktioniert hier nicht. // Jedes Element wird zu einer Referenz auf das gleiche // Array, was eine ganz schlechte Sache wäre.
Die Zellen einer leeren Matrix enthalten zu Beginn den Wert undefined. Sollen sie einen anderen Anfangswert haben, müssen wir diesen explizit setzen. Ja, JavaScript hätte Matrizen besser unterstützen sollen. Wir können aber auch das korrigieren:
Max. Linie
Array.matrix = function (m, n, initial) { var a, i, j, mat = []; for (i = 0; i < m; i += 1) { a = []; for (j = 0; j < n; j += 1) { a[j] = initial; } mat[i] = a; } return mat; };
Dimensionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
69
Links // Erzeuge eine mit Nullen gefüllte 4*4-Matrix. var myMatrix = Array.matrix(4, 4, 0); document.writeln(myMatrix[3][3]);
// 0
// Methode zum Aufbau einer Einheitsmatrix. Array.identity = function (n) { var i, mat = Array.matrix(n, n, 0); for (i = 0; i < n; i += 1) { mat[i][i] = 1; } return mat; }; myMatrix = Array.identity(4); document.writeln(myMatrix[3][3]);
// 1
Max. Linie
Max. Linie 70
|
Kapitel 6: Arrays
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 7
KAPITEL 7
Reguläre Ausdrücke
Whereas the contrary bringeth bliss, And is a pattern of celestial peace. Whom should we match with Henry, being a king… – William Shakespeare, The First Part of Henry the Sixth
Viele Features hat JavaScript von anderen Sprachen übernommen. Die Syntax stammt von Java, Funktionen von Scheme und die prototypische Vererbung von Self. Reguläre Ausdrücke hat JavaScript von Perl übernommen. Ein regulärer Ausdruck ist die Spezifikation der Syntax einer einfachen Sprache. Reguläre Ausdrücke werden mit Methoden verwendet, um Informationen in Strings zu suchen, zu ersetzen und zu extrahieren. Die mit regulären Ausdrücken arbeitenden Methoden sind regex.exec, regex.test, string.match, string.replace, string. search und string.split. All diese Methoden werden in Kapitel 8 erläutert. Reguläre Ausdrücke haben bei JavaScript üblicherweise einen deutlichen Performance-Vorteil gegenüber gleichwertigen String-Operationen. Reguläre Ausdrücke sind das Ergebnis einer mathematischen Studie formaler Sprachen. Ken Thompson übertrug Stephen Kleenes theoretische Arbeit zu Typ-3-Sprachen in einen praktischen Pattern-Matcher, der in Tools wie Texteditoren und Programmiersprachen eingebettet werden konnte.
Max. Linie
Die Syntax regulärer Ausdrücke hält sich bei JavaScript eng an die Originalformulierungen der Bell Labs, wobei einige Reinterpretationen und Erweiterungen von Perl übernommen wurden. Die Regeln zum Schreiben regulärer Ausdrücke können überraschend komplex sein, weil Zeichen an manchen Stellen als Operatoren interpretiert werden und an anderen Stellen als Literale. Dadurch sind reguläre Ausdrücke nicht nur schwer zu schreiben, sondern auch (und das ist viel schlimmer) schwer zu lesen, und es ist gefährlich, sie zu modifizieren. Es ist notwendig, ein recht umfassendes Verständnis von der Komplexität regulärer Ausdrücke zu besitzen, um sie richtig lesen zu können. Um dem entgegenzuwirken, habe ich die Regeln etwas vereinfacht. So wie sie hier vorgestellt werden, sind sie etwas weniger
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
71
Max. Linie
Links knapp, können aber auch etwas einfacher richtig eingesetzt werden. Und das ist auch gut so, weil die Pflege und das Debugging regulärer Ausdrücke sehr schwierig sein kann. Heutige reguläre Ausdrücke sind streng genommen nicht regulär, können aber sehr nützlich sein. Reguläre Ausdrücke neigen dazu, extrem knapp, ja sogar kryptisch zu sein. In ihrer simpelsten Form sind sie einfach zu nutzen, können aber schnell verwirrend werden. Die regulären Ausdrücke von JavaScript sind schwer zu lesen, was zum Teil daran liegt, dass keine Kommentare und Whitespaces erlaubt sind. Alle Teile eines regulären Ausdrucks werden auf kleinstem Raum zusammengefasst, was es fast unmöglich macht, sie zu entziffern. Das ist bei Sicherheitsanwendungen von besonderer Bedeutung, wo sie zum Scannen und Validieren verwendet werden. Wenn Sie einen regulären Ausdruck nicht lesen und verstehen können, wie können Sie dann sicher sein, dass er alle Eingaben richtig verarbeitet? Doch trotz dieser offensichtlichen Nachteile werden reguläre Ausdrücke sehr häufig eingesetzt.
Ein Beispiel Hier ein Beispiel. Es handelt sich um einen regulären Ausdruck zur Erkennung von URLs. Die Seiten dieses Buches sind nicht unendlich breit, weshalb ich den regulären Ausdruck auf zwei Zeilen aufgeteilt habe. In einem JavaScript-Programm müsste er in einer einzigen Zeile stehen. Whitespace ist relevant: var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+) (?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/; var url = "http://www.ora.com:80/goodparts?q#fragment";
Rufen wir die exec-Methode von parse_url auf. Wenn sie den von uns übergebenen String erfolgreich erkennen kann, liefert sie uns ein Array zurück, das die aus dem url extrahierten Teile enthält: var url = "http://www.ora.com:80/goodparts?q#fragment"; var result = parse_url.exec(url); var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash']; var blanks = ' var i;
';
for (i = 0; i < names.length; i += 1) { document.writeln(names[i] + ':' + blanks.substring(names[i].length), result[i]); }
Max. Linie
Das Ergebnis sieht wie folgt aus:
72
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts url: scheme: slash: host: port: path: query: hash:
http://www.ora.com:80/goodparts?q#fragment http // www.ora.com 80 goodparts q fragment
In Kapitel 2 haben wir Syntaxdiagramme verwendet, um die JavaScript-Sprache zu beschreiben. Wir können diese auch verwenden, um die Sprachen zu beschreiben, die durch reguläre Ausdrücke definiert werden. Das macht es einfacher zu erkennen, was ein regulärer Ausdruck macht. Hier sehen Sie ein Syntaxdiagramm für parse_url.
Max. Linie
Reguläre Ausdrücke können nicht wie Funktionen in kleinere Teile zerlegt werden, weshalb das Diagramm, das parse_url repräsentiert, recht groß ist.
Ein Beispiel | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
73
Max. Linie
Links Lassen Sie uns parse_url in seine einzelnen Teile zerlegen, um zu sehen, wie es funktioniert: ^
Das Zeichen ^ steht für den Anfang des Strings. Es handelt sich um einen Anker, der verhindert, dass exec ein nicht-URL-artiges Präfix überspringt: (?:([A-Za-z]+):)?
Dieser Faktor erkennt den Namen eines Schemas, aber nur, wenn ihm ein : (Doppelpunkt) folgt. Das (?:…) steht für eine nicht einfangende (noncapturing) Gruppe. Der Suffix ? gibt an, dass diese Gruppe optional ist. Sie bedeutet wiederhole null oder mehre Male. Das (…) steht für eine einfangende (capturing) Gruppe. Eine einfangende Gruppe kopiert den erkannten Text und legt ihn im Array result ab. Jeder einfangenden Gruppe wird eine Nummer zugewiesen. Diese erste einfangende Gruppe ist die 1, d.h., eine Kopie des durch die einfangende Gruppe erkannten Textes steht in result[1]. Das […] steht für eine Zeichenklasse. Diese Zeichenklasse, A-Za-z, enthält 26 Groß- und 26 Kleinbuchstaben. Die Bindestriche bezeichnen Wertebereiche, hier also von A bis Z. Das Suffix + gibt an, dass die Zeichenklasse ein- oder mehrmals erkannt wird. Der Gruppe folgt das Zeichen :, das als Literal erkannt wird: (\/{0,3})
Den nächsten Faktor bildet die einfangende Gruppe 2. \/ steht für ein zu erkennendes /-Zeichen (Slash). Es wird durch ein \ (Backslash) geschützt, damit es nicht als das Ende des Regex-Literals fehlinterpretiert wird. Das Suffix {0,3} besagt, dass das / 0-, 1-, 2- oder 3-mal erkannt wird: ([0-9.\-A-Za-z]+)
Der nächste Faktor ist die einfangende Gruppe 3. Sie erkennt einen Hostnamen, der aus einer oder mehreren Ziffern, Buchstaben, Punkten (.) oder Minuszeichen (-) besteht. Das Minuszeichen wird durch \- geschützt, damit es nicht als Bereichsbindestrich fehlinterpretiert wird: (?::(\d+))?
Der nächste Teil erkennt eine optionale Portnummer, die aus einer Folge von einer oder mehreren Ziffern besteht, der ein Doppelpunkt ( :) vorangestellt ist. Das \d steht für eine Ziffer. Die Folge von einer oder mehreren Ziffern bildet die einfangende Gruppe 4: (?:\/([^?#]*))?
Max. Linie
Es gibt eine weitere optionale Gruppe. Diese beginnt mit einem /. Die Zeichenklasse [^?#] beginnt mit einem ^, was bedeutet, dass die Klasse alle Zeichen einschließt außer ? und #. Das * legt fest, dass die Zeichenklasse null- oder mehrmals erkannt wird.
74
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Beachten Sie, dass ich hier etwas schluderig bin. Die Klasse aller Zeichen außer ? und # schließt Zeilenende-Zeichen, Steuerzeichen und viele andere Zeichen ein, die hier eigentlich nicht erkannt werden sollten. In den meisten Fällen geht das gut, aber es bleibt ein Restrisiko, dass falscher Text durchschlüpfen könnte. Nachlässige reguläre Ausdrücke sind eine häufige Ursache von Sicherheitslücken. Es ist wesentlich einfacher, schludrige reguläre Ausdrücke zu entwickeln, als saubere: (?:\?([^#]*))?
Nun folgt eine optionale Gruppe, die mit einem ? beginnt. Sie enthält die einfangende Gruppe 6, die null oder mehr Zeichen enthält, die nicht # sind: (?:#(.*))?
Es gibt eine letzte optionale Gruppe, die mit einem # beginnt. Der . erkennt jedes Zeichen außer dem Zeilenende: $
Das $ repräsentiert das Ende des Strings. Es stellt sicher, dass auf das Ende der URL kein zusätzliches Material folgt. Das sind also die einzelnen Bestandteile des regulären Ausdrucks parse_url.1 Es ist möglich, reguläre Ausdrücke aufzubauen, die komplexer sind als parse_url, aber ich möchte Ihnen das nicht empfehlen. Reguläre Ausdrücke sind am besten, wenn sie kurz und einfach sind. Nur dann können Sie sicher sein, dass sie korrekt funktionieren und bei Bedarf erfolgreich modifiziert werden können. Es gibt ein hohes Maß an Kompatibilität zwischen den JavaScript-Sprachprozessoren. Der Teil der Sprache, der am wenigsten portabel ist, ist die Implementierung regulärer Ausdrücke. Sehr komplizierte oder überladene reguläre Ausdrücke haben sehr wahrscheinlich mit Portabilitätsproblemen zu kämpfen. Geschachtelte reguläre Ausdrücke können bei einigen Implementierungen auch ernsthafte Performance-Probleme haben. Einfachheit ist die beste Strategie. Sehen wir uns ein weiteres Beispiel an: einen regulären Ausdruck, der Zahlen erkennt. Zahlen bestehen aus einem ganzzahligen Teil mit einem optionalen Minuszeichen, gefolgt von einem optionalen Nachkomma-Teil und einem optionalen Exponenten: var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i; var test = function (num) { document.writeln(parse_number.test(num)); };
Max. Linie
1
Packt man alles zusammen, ist es visuell doch sehr verwirrend:
Max. Linie
/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(. *))?$/
Ein Beispiel | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
75
Links test('1'); test('number'); test('98.6'); test('132.21.86.100'); test('123.45E-67'); test('123.45D-67');
// // // // // //
true false true false true false
parse_number identifiziert erfolgreich die Strings, die unserer Spezifikation entspre-
chen bzw. nicht entsprechen. Bei denjenigen Strings, bei denen es keinen Treffer gibt, werden aber keinerlei Informationen darüber zurückgeliefert, warum der Test fehlgeschlagen ist.
Sehen wir uns parse_number genauer an: /^
$/i
Wir nutzen erneut ^ und $, um den regulären Ausdruck zu verankern. Damit stellen wir sicher, dass alle Zeichen im Text mit dem regulären Ausdruck verglichen werden. Würden wir die Anker weglassen, würde uns der reguläre Ausdruck mitteilen, ob der String eine Zahl enthält. Mit den Ankern teilt er uns mit, ob der String nur aus einer Zahl besteht. Würden wir nur das ^ aufnehmen, würden nur Strings erkannt werden, die mit einer Zahl beginnen. Hätten wir nur das $ genutzt, würden nur Strings erkannt werden, die mit einer Zahl enden. Das Flag i sorgt dafür, dass die Groß-/Kleinschreibung beim Matching von Buchstaben ignoriert wird. Der einzige Buchstabe in unserem Muster ist das e. Wir wollen, dass bei e auch E erkannt wird. Wir hätten das e-Element auch als [Ee] oder (?: E|e) formulieren können, aber das war nicht nötig, da wir das Flag i nutzen: -?
Max. Linie
Max. Linie 76
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Das Suffix ? beim Minuszeichen zeigt an, dass das Minuszeichen optional ist: \d+
\d ist das Gleiche wie [0-9]. Es erkennt eine Ziffer. Das Suffix + sorgt dafür, dass
eine oder mehrere Ziffern erkannt werden: (?:\.\d*)?
Das (?:...)? steht für eine optionale nicht einfangende Gruppe. Es ist üblicherweise besser, mit nicht einfangenden Gruppen zu arbeiten, da die weniger hässlichen einfangenden Gruppen sich negativ auf die Performance auswirken. Diese Gruppe erkennt den Dezimalpunkt, gefolgt von null oder mehr Ziffern: (?:e[+\-]?\d+)?
Dies ist eine weitere optionale nicht einfangende Gruppe. Sie erkennt e (oder E), ein optionales Vorzeichen und eine oder mehrere Ziffern.
Konstruktion Es gibt zwei Möglichkeiten, ein RegExp-Objekt zu erzeugen. Der bevorzugte Weg, den wir auch in den Beispielen verwendet haben, besteht in der Verwendung eines Regex-Literals.
Literale regulärer Ausdrücke stehen zwischen Slashes. Das ist etwas kniffelig, da der Slash auch als Divisionsoperator und bei Kommentaren verwendet wird. Es gibt drei Flags, die bei einer RegExp gesetzt werden können. Diese werden durch die Buchstaben g, i und m repräsentiert, deren Bedeutung in Tabelle 7-1 aufgeführt ist. Die Flags werden direkt an das Ende des RegExp-Literals angehängt: // Regex-Objekt, das einen Javascript-String erkennt. var my_regex = /"(?:\\.|[^\\\"])*"/g;
Tabelle 7-1: Flags für reguläre Ausdrücke
Max. Linie
Flag
Bedeutung
g
Globales Matching (Matching läuft über mehrere Zeilen. Die genaue Bedeutung ist von Methode zu Methode unterschiedlich.)
i
Insensitive (ignoriere Groß-/Kleinschreibung)
m
Multiline (^ und $ erkennen Zeilenende-Zeichen)
Konstruktion | This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 77
Links Die andere Möglichkeit, einen regulären Ausdruck aufzubauen, bildet der RegExpKonstruktor. Der Konstruktor erwartet einen String und übersetzt ihn in ein RegExpObjekt. Sie müssen beim Aufbau dieses Strings vorsichtig sein, da Backslashes in regulären Ausdrücken eine etwas andere Bedeutung haben als in String-Literalen. Üblicherweise müssen Sie jedem literalen Backslash einen weiteren Backslash voranstellen und auch die Anführungszeichen mit einem Escape-Zeichen schützen: // Regex-Ausdruck, der einen JavaScript-String erkennt. var my_regex = new RegExp("\"(?:\\.|[^\\\\\\\"])*\"", 'g');
Der zweite Parameter ist ein String, der die Flags angibt. Der RegExp-Konstruktor ist nützlich, wenn ein regulärer Ausdruck zu Laufzeit generiert werden muss, weil das entsprechende Datenmaterial für den Programmierer nicht verfügbar war. RegExp-Objekte können die in Tabelle 7-2 aufgeführten Eigenschaften enthalten. Tabelle 7-2: Eigenschaften von RegExp-Objekten Eigenschaft
Verwendung
global
wahr, wenn das g-Flag verwendet wurde
ignoreCase
wahr, wenn das i-Flag verwendet wurde
lastIndex
Der Index, an dem der nächste exec -Match beginnen soll. Zu Beginn ist dieser Wert null
multiline
wahr, wenn das m-Flag verwendet wurde
source
Der Quelltext des regulären Ausdrucks
Aus Regex-Literalen aufgebaute RegExp-Objekte teilen sich eine einzelne Instanz: function make_a_matcher( ) { return /a/gi; } var x = make_a_matcher( ); var y = make_a_matcher( ); // Vorsicht: x und y sind das gleiche Objekt! x.lastIndex = 10; document.writeln(y.lastIndex);
// 10
Elemente Max. Linie
Sehen wir uns etwas genauer an, aus welchen Elementen reguläre Ausdrücke bestehen.
78
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Regex-Auswahl
Eine Regex-Auswahl besteht aus einer oder mehreren Regex-Sequenzen. Die Sequenzen werden durch das Zeichen | (vertikaler Balken) getrennt. Die Auswahl wird erkannt, wenn eine der Sequenzen erkannt wird. Das Matching der Sequenzen erfolgt in der angegebenen Reihenfolge. Das heißt,: "into".match(/in|int/)
erkennt das in in into. Das int würde nicht erkannt werden, da das Matching von in erfolgreich war.
Regex-Sequenzen
Eine Regex-Sequenz enthält eine oder mehrere Regex-Faktoren. Jedem Faktor kann optional ein Quantifier folgen, der festlegt, wie oft der Faktor vorkommen darf. Ohne Quantifier wird der Faktor genau einmal gematcht.
Regex-Faktor
Max. Linie
Max. Linie Elemente This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
79
Links Ein Regex-Faktor kann ein Zeichen, eine geklammerte Gruppe, eine Zeichenklasse oder eine Escape-Sequenz sein. Alle Zeichen werden als Literale betrachtet, mit Ausnahme der folgenden Steuer- und Sonderzeichen: \ / [ ] ( ) { } ? + * | . ^ $
Diesen muss das Präfix \ vorangestellt werden, wenn sie literal erkannt werden sollen. Im Zweifel können Sie jedem Sonderzeichen ein \ voranstellen, um es zu einem Literal zu machen. Das \-Präfix macht Buchstaben und Ziffern nicht zu Literalen. Ein nicht geschütztes . erkennt jedes Zeichen außer dem Zeilenende-Zeichen. Ein nicht geschütztes ^ erkennt den Anfang des Textes, wenn die Eigenschaft lastIndex null ist. Es kann auch Zeilenende-Zeichen erkennen, wenn das m-Flag angegeben wurde. Ein ungeschütztes $ erkennt das Ende des Textes. Es kann auch Zeilenende-Zeichen erkennen, wenn das m-Flag angegeben wurde.
Regex-Escape
Das Backslash-Zeichen dient in Regex-Faktoren und in Strings als Escape-Zeichen, funktioniert bei Regex-Faktoren aber etwas anders.
Max. Linie
Wie bei Strings ist \f das Formfeed-Zeichen (Seitenvorschub), \n ist das NewlineZeichen (Zeilenvorschub), \r ist das Carriage-Return-Zeichen (Wagenrücklauf), \t ist das Tabulator-Zeichen, und \u erlaubt die Angabe eines Unicode-Zeichens als 16Bit-Hex-Konstante. In Regex-Faktoren ist \b aber nicht das Backspace-Zeichen. \d ist das Gleiche wie [0-9]. Es erkennt eine Ziffer. \D ist das Gegenstück: [^0-9].
80
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts \s ist das Gleiche wie [\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]. Dies entspricht einer Teilmenge der Unicode-Whitespace-Zeichen. \S ist das Gegenstück: [^\f\n\ r\t\u000B\u0020\u00A0\u2028\u2029]. \w ist das Gleiche wie [0-9A-Z_a-z]. \W ist das Gegenstück: [^0-9A-Z_a-z]. Das soll
die Zeichen repräsentieren, die in Wörtern enthalten sind. Leider ist die definierte Klasse völlig ungeeignet, um mit einer realen Sprache zu arbeiten. Müssen Sie eine Klasse von Buchstaben erkennen, müssen Sie eine eigene Klasse von Buchstaben festlegen. Eine einfache Zeichenklasse ist [A-Za-z\u00C0-\u1FFF\u2800-\uFFFD]. Sie umfasst alle Unicode-Buchstaben, aber auch Tausende von Zeichen, die keine Buchstaben sind. Unicode ist groß und komplex. Eine exakte Buchstabenklasse der »Basic Multilingual Plane« ist möglich, wäre aber sehr groß und ineffektiv. Die regulären Ausdrücke von JavaScript unterstützen die Internationalsierung nur sehr schlecht. \b ist als Wortgrenzen-Anker gedacht, der die Erkennung von Text an Wortgrenzen vereinfachen soll. Unglücklicherweise verwendet er \w, um Wortgrenzen zu finden, weshalb er für multilinuguale Anwendungen völlig ungeeignet ist. Das ist keine gute Eigenschaft. \1 ist eine Referenz auf den Text, der durch die Gruppe 1 eingefangen wurde. Auf
diese Weise kann der erkannte Text wiederverwendet werden. Zum Beispiel können Sie einen Text wie folgt nach doppelt vorkommenden Wörtern durchsuchen: var doubled_words = /([A-Za-z\u00C0-\u1FFF\u2800-\uFFFD'\-]+)\s+\1/gi;
doubled_words sucht nach Vorkommen von Wörtern (Strings mit einem oder mehreren Buchstaben), gefolgt von einem Whitespace, gefolgt vom gleichen Wort. \2 ist eine Referenz auf die Gruppe 2, \3 ist eine Referenz auf die Gruppe 3 und so weiter.
Regex-Gruppe
Max. Linie
Max. Linie Elemente This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
81
Links Es gibt vier Arten von Gruppen: Einfangende Gruppen (capturing) Eine einfangende Gruppe ist eine in Klammern stehende Regex-Auswahl. Die innerhalb der Gruppe erkannten Zeichen werden festgehalten. Jeder einfangenden Gruppe wird eine Nummer zugewiesen. Das erste einfangende ( im regulären Ausdruck ist die Gruppe 1. Das zweite einfangende ( im regulären Ausdruck ist die Gruppe 2. Nicht einfangende Gruppen (noncapturing) Eine nicht einfangende Gruppe verwendet das Präfix (?:. Eine nicht einfangende Gruppe dient einfach nur dem Matching. Sie hält den erkannten Text nicht fest. Das hat den Vorteil einer etwas besseren Performance. Nicht einfangende Gruppen haben keine Auswirkungen auf die Nummerierung einfangender Gruppen. Positiver Lookahead Eine Gruppe mit positivem Lookahead verwendet das Präfix (?=. Sie verhält sich wie eine nicht einfangende Gruppe, nur dass nach dem Erkennen der Gruppe der Text an den Anfang dieser Gruppe »zurückgespult« wird. Das heißt: Effektiv wird nichts erkannt. Das ist keine gute Seite von JavaScript. Negativer Lookahead Eine Gruppe mit negativem Lookahead verwendet das Präfix (?!. Sie entspricht einer Gruppe mit positivem Lookahead, nur dass sie einen Treffer signalisiert, wenn das Matching fehlschlägt. Das ist keine gute Seite von JavaScript.
Regex-Klasse
Eine Regex-Klasse ist eine praktische Möglichkeit, um eine oder mehrere Gruppen von Zeichen zu spezifizieren. Wollen Sie beispielsweise einen Vokal erkennen, könnten Sie (?:a|e|i|o|u) angeben, aber man kann das als Klasse [aeiou] bequemer formulieren.
Max. Linie
Max. Linie 82
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Klassen bieten zwei weitere Vorteile. Zum einen können Sie Zeichenbereiche angeben. Die Gruppe der 32 ASCII-Sonderzeichen ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
könnte man also wie folgt schreiben: (?:!|"|#|\$|%|&|'|\(|\)|\*|\+|,|-|\.|\/|:|;|<|=|>|@|\[|\\|]|\^|_|` |\{|\||\}|~)
Die folgende Schreibweise ist aber doch etwas angenehmer: [!-\/:-@\[-`{-~]
Sie umfasst die Zeichen von ! bis / und : bis @ sowie [ bis ` und { bis ~. Dennoch sieht das noch ziemlich scheußlich aus. Der andere Vorteil ist die Komplementierung einer Klasse. Ist das erste Zeichen hinter dem [ ein ^, dann schließt die Klasse die angegebenen Zeichen aus. Daher erkennt [^!-\/:-@\[-`{-~] alle Zeichen, die keine ASCII-Sonderzeichen sind.
Regex-Klassen-Escape
Die Regeln für das Escaping innerhalb von Zeichenklassen sind etwas anders als die für einen Regex-Faktor. [\b] ist das Backspace-Zeichen. Folgende Sonderzeichen benötigen innerhalb einer Zeichenklasse ein Escaping: - / [ \ ] ^
Max. Linie
Max. Linie Elemente This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
83
Links Regex-Quantifier
Ein Regex-Faktor kann ein Regex-Quantifier-Suffix besitzen, das bestimmt, wie oft der Faktor vorkommen kann. Eine in geschweiften Klammern stehende Zahl bedeutet, dass der Faktor exakt so oft vorkommen kann. /www/ erkennt also das Gleiche wie /w{3}/. {3,6} steht für 3, 4, 5 oder 6 Matches, und {3,} erkennt 3 oder mehr Treffer. ? ist das Gleiche wie {0,1}. * ist das Gleiche wie {0,}, und + ist das Gleiche wie {1,}.
Matching-Operationen sind üblicherweise »gierig« (greedy), d.h., sie erkennen bis zum angegebenen Limit (wenn es eines gibt) so viele Wiederholungen wie möglich. Enthält der Quantifier ein zusätzliches ?-Suffix, dann ist das Matching »träge« (lazy), d.h., es wird versucht, so wenige Wiederholungen wie möglich zu erkennen. Üblicherweise ist es besser, am gierigen Matching festzuhalten.
Max. Linie
Max. Linie 84
|
Kapitel 7: Reguläre Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 8
KAPITEL 8
Methoden
Though this be madness, yet there is method in ’t. – William Shakespeare, The Tragedy of Hamlet, Prince of Denmark
JavaScript besitzt einen kleinen Satz von Standardmethoden, die den Standardtypen zur Verfügung stehen.
Array array.concat(element…) Die concat-Methode erzeugt ein neues Array, das aus einer einfachen Kopie des arrays und den angehängten elementen besteht. Ist ein element ein Array, dann wird jedes seiner Elemente einzeln angehängt (siehe auch array.push(element...) weiter unten in diesem Kapitel). var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c = a.concat(b, true); // c ist ['a', 'b', 'c', 'x', 'y', 'z', true]
array.join(separator) Die join-Methode erzeugt einen String aus einem array. Dies geschieht, indem jedes Element des arrays in einen String umgewandelt wird und diese Strings dann über den separator verknüpft werden. Der Standardseparator ist ','. Soll join ohne Separator durchgeführt werden, verwenden Sie den leeren String als separator. Wenn Sie einen String aus einer großen Zahl von Elementen aufbauen, ist es üblicherweise schneller, die einzelnen Teile in einem Array abzulegen und dann über join zu verketten, anstatt sie über den Operator + zusammenzufügen:
Max. Linie
var a = ['a', 'b', 'c']; a.push('d'); var c = a.join(''); // c ist 'abcd';
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
85
Links array.pop( ) Die Methoden pop und push lassen ein array wie einen Stack funktionieren. Die pop-Methode entfernt das letzte Element aus dem array und liefert es zurück. Ist das array leer, wird undefined zurückgegeben. var a = ['a', 'b', 'c']; var c = a.pop( ); // a ist ['a', 'b'] & c ist 'c'
pop kann wie folgt implementiert werden: Array.method('pop', function ( ) { return this.splice(this.length - 1, 1)[0]; });
array.push(element…) Die push-Methode hängt elemente an das Ende des Arrays an. Im Gegensatz zur concatMethode modifiziert sie das array und hängt Array-Elemente als Ganzes an. Sie gibt die neue Länge des arrays zurück: var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c = a.push(b, true); // a ist ['a', 'b', 'c', ['x', 'y', 'z'], true] // c ist 5;
push kann wie folgt implementiert werden: Array.method('push', function ( ) { this.splice.apply( this, [this.length, 0]. concat(Array.prototype.slice.apply(arguments))); return this.length; });
array.reverse( ) Die reverse-Methode kehrt die Reihenfolge der Elemente im array um. Sie gibt das array zurück: var a = ['a', 'b', 'c']; var b = a.reverse( ); // sowohl a als auch b sind ['c', 'b', 'a']
array.shift( ) Die shift-Methode entfernt das erste Element aus einem array und gibt es zurück. Ist das array leer, wird undefined zurückgegeben. shift ist üblicherweise wesentlich langsamer als pop: var a = ['a', 'b', 'c']; var c = a.shift( ); // a is ['b', 'c'] & c is 'a'
shift kann wie folgt implementiert werden:
Max. Linie
Array.method('shift', function ( ) { return this.splice(0, 1)[0]; });
86
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts array.slice(anfang, ende) Die slice-Methode erzeugt eine einfache Kopie eines Teils eines arrays. Das erste kopierte Element ist dabei array[anfang]. Der Vorgang endet vor array[ende]. Der ende -Parameter ist optional, voreingestellt ist array.length. Ist einer der Parameter negativ, wird ihm array.length hinzuaddiert, in dem Versuch, den Wert positiv werden zu lassen. Ist start größer oder gleich array.length, dann ist das Ergebnis ein neues leeres Array. Verwechseln Sie slice nicht mit splice (siehe auch string.slice weiter unten in diesem Kapitel). var var var var
a b c d
= = = =
['a', 'b', 'c']; a.slice(0, 1); a.slice(1); a.slice(1, 2);
// b ist ['a'] // c ist ['b', 'c'] // d ist ['b']
array.sort(vergleichsfn) Die sort-Methode sortiert den Inhalt eines arrays »in place«. Arrays von Zahlen werden nicht korrekt sortiert: var n = [4, 8, 15, 16, 23, 42]; n.sort( ); // n ist [15, 16, 23, 4, 42, 8]
Die Standard-Vergleichsfunktion von JavaScript geht davon aus, dass die zu sortierenden Elemente Strings sind. Sie ist nicht schlau genug, den Elemtenttyp vor dem Vergleich zu überprüfen, und wandelt daher die Zahlen während des Vergleichs in Strings um, was zu einem schockierend falschen Ergebnis führt. Glücklicherweise kann man die Vergleichsfunktion durch eine eigene ersetzen. Ihre Vergleichsfunktion muss zwei Parameter verarbeiten und 0 zurückliefern, wenn diese beiden Parameter gleich sind. Sie muss eine negative Zahl liefern, wenn der erste Parameter vor dem zweiten einsortiert werden soll, und eine positive Zahl, wenn der zweite Parameter zuerst vor den ersten gehört. (Alte Hasen werden sich an die arithmetische IF-Anweisung von FORTRAN II erinnert fühlen.) n.sort(function (a, b) { return a - b; }); // n ist [4, 8, 15, 16, 23, 42];
Diese Funktion sortiert Zahlen, nicht aber Strings. Wollen wir jedes Array einfacher Werte sortieren, müssen wir etwas mehr tun:
Max. Linie
var m = ['aa', 'bb', 'a', 4, 8, 15, 16, 23, 42]; m.sort(function (a, b) { if (a === b) { return 0; } if (typeof a === typeof b) { return a < b ? -1 : 1; } return typeof a < typeof b ? -1 : 1; }); // m ist [4, 8, 15, 16, 23, 42, 'a', 'aa', 'bb']
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Array
|
87
Links Ist die Groß-/Kleinschreibung unerheblich, kann die Vergleichsfunktion die Operanden vor dem Vergleich in Kleinbuchstaben umwandeln (siehe auch string.localeCompare weiter unten in diesem Kapitel). Mit einer schlaueren Vergleichsfunktion können wir ein Array von Objekten sortieren. Um die Dinge für den allgemeinen Fall zu vereinfachen, schreiben wir eine Funktion, die Vergleichsfunktionen erzeugt: // Die Funktion by erwartet einen Member-Namensstring und // gibt eine Vergleichsfunktion zurück, die verwendet werden kann, // um ein Array von Objekten zu sortieren, das dieses Member enthhält. var by = function (name) { return function (o, p) { var a, b; if (typeof o === 'object' && typeof p === 'object' && o && p) { a = o[name]; b = p[name]; if (a === b) { return 0; } if (typeof a === typeof b) { return a < b ? -1 : 1; } return typeof a < typeof b ? -1 : 1; } else { throw { name: 'Error', message: 'Expected an object when sorting by ' + name; }; } }; }; var s = [ {first: 'Joe', last: 'Besser'}, {first: 'Moe', last: 'Howard'}, {first: 'Joe', last: 'DeRita'}, {first: 'Shemp', last: 'Howard'}, {first: 'Larry', last: 'Fine'}, {first: 'Curly', last: 'Howard'} ]; s.sort(by('first')); // s is [ // {first: 'Curly', last: 'Howard'}, // {first: 'Joe', last: 'DeRita'}, // {first: 'Joe', last: 'Besser'}, // {first: 'Larry', last: 'Fine'}, // {first: 'Moe', last: 'Howard'}, // {first: 'Shemp', last: 'Howard'} // ]
Max. Linie
Max. Linie 88
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Die sort-Methode ist nicht stabil, weshalb s.sort(by('first')).sort(by('last'));
nicht garantieren kann, die korrekte Sequenz zu erzeugen. Wenn Sie über mehrere Schlüssel sortieren wollen, müssen Sie wieder etwas mehr Arbeit investieren. Wir können by um einen zweiten Parameter erweitern: eine weitere Vergleichsmethode, die aufgerufen wird, um Verbindungen aufzubrechen, wenn der Hauptschlüssel einen Treffer erzeugt: // // // // // //
Die Funktion by erwartet einen Member-Namensstring sowie eine optionale untergeordnete Vergleichsfunktion und gibt eine Vergleichsfunktion zurück, die zur Sortierung eines Arrays von Objekten verwendet werden kann, die dieses Member enthalten. Die untergeordnete Vergleichsfunktion wird verwendet, um Verbindungen aufzubrechen, wenn o[name] und p[name] gleich sind.
var by = function (name, minor) { return function (o, p) { var a, b; if (o && p && typeof o === 'object' && typeof p === 'object') { a = o[name]; b = p[name]; if (a === b) { return typeof minor === 'function' ? minor(o, p) : 0; } if (typeof a === typeof b) { return a < b ? -1 : 1; } return typeof a < typeof b ? -1 : 1; } else { throw { name: 'Error', message: 'Expected an object when sorting by ' + name; }; } }; }; s.sort(by('last', by('first'))); // s is [ // {first: 'Joe', last: 'Besser'}, // {first: 'Joe', last: 'DeRita'}, // {first: 'Larry', last: 'Fine'}, // {first: 'Curly', last: 'Howard'}, // {first: 'Moe', last: 'Howard'}, // {first: 'Shemp', last: 'Howard'} // ]
array.splice(anfang, löschZähler, element…)
Max. Linie
Die splice-Methode entfernt Elemente aus einem array und ersetzt diese durch neue elemente. Der Parameter anfang gibt die Position innerhalb des arrays an. Der Parameter löschZähler gibt die Anzahl der Elemente an, die an dieser Position gelöscht werden sollen. Gibt es weitere Parameter, werden diese elemente an der Position eingefügt. Zurückgegeben wird ein Array mit den gelöschten Elementen.
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Array
|
89
Max. Linie
Links Das Hauptanwendungsgebiet für splice ist das Löschen von Elementen aus einem Array. Verwechseln Sie splice nicht mit slice: var a = ['a', 'b', 'c']; var r = a.splice(1, 1, 'ache', 'bug'); // a ist ['a', 'ache', 'bug', 'c'] // r ist ['b']
splice kann wie folgt implementiert werden: Array.method('splice', function (start, deleteCount) { var max = Math.max, min = Math.min, delta, element, insertCount = max(arguments.length - 2, 0), k = 0, len = this.length, new_len, result = [], shift_count; start = start || 0; if (start < 0) { start += len; } start = max(min(start, len), 0); deleteCount = max(min(typeof deleteCount === 'number' ? deleteCount : len, len - start), 0); delta = insertCount - deleteCount; new_len = len + delta; while (k < deleteCount) { element = this[start + k]; if (element !== undefined) { result[k] = element; } k += 1; } shift_count = len - start - deleteCount; if (delta < 0) { k = start + insertCount; while (shift_count) { this[k] = this[k - delta]; k += 1; shift_count -= 1; } this.length = new_len; } else if (delta > 0) { k = 1; while (shift_count) { this[new_len - k] = this[len - k]; k += 1; shift_count -= 1; }
Max. Linie 90
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts } for (k = 0; k < insertCount; k += 1) { this[start + k] = arguments[k + 2]; } return result; });
array.unshift(element…) Die unshift-Methode ähnelt der push-Methode, fügt die elemente aber am Anfang des arrays ein und nicht am Ende. Sie gibt die neue Länge des arrays zurück: var a = ['a', 'b', 'c']; var r = a.unshift('?', '@'); // a ist ['?', '@', 'a', 'b', 'c'] // r ist 5
unshift kann wie folgt implementiert werden: Array.method('unshift', function ( ) { this.splice.apply(this, [0, 0].concat(Array.prototype.slice.apply(arguments))); return this.length; });
Function function.apply(thisArg, argArray) Die apply-Methode ruft eine funktion auf. Sie erwartet ein Objekt, das an this gebunden wird, sowie ein optionales Array von Argumenten. Die apply-Methode wird im applyAufrufmuster (Kapitel 4) verwendet: Function.method('bind', function (that) { // Gibt eine Funktion zurück, die diese Funktion aufruft, // als wäre sie eine Methode dieses Objekts. var method = this, slice = Array.prototype.slice, args = slice.apply(arguments, [1]); return function ( ) { return method.apply(that, args.concat(slice.apply(arguments, [0]))); }; }); var x = function ( ) { return this.value; }.bind({value: 666}); alert(x( )); // 666
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Function |
91
Links Number number.toExponential(nachkommastellen) Die toExponential-Methode wandelt einen number-Wert in einen String in exponentieller Darstellung um. Der optionale Parameter nachkommastellen legt die Anzahl der Nachkommastellen fest. Er muss zwischen 0 und 20 liegen. document.writeln(Math.PI.toExponential(0)); document.writeln(Math.PI.toExponential(2)); document.writeln(Math.PI.toExponential(7)); document.writeln(Math.PI.toExponential(16)); document.writeln(Math.PI.toExponential( )); // Erzeugt 3e+0 3.14e+0 3.1415927e+0 3.1415926535897930e+0 3.141592653589793e+0
number.toFixed(nachkommastellen) Die toFixed-Methode wandelt einen number-Wert in einen Dezimal-String um. Der optionale Parameter nachkommastellen legt die Anzahl der Nachkommastellen fest. Er muss zwischen 0 und 20 liegen. Voreingestellt ist 0: document.writeln(Math.PI.toFixed(0)); document.writeln(Math.PI.toFixed(2)); document.writeln(Math.PI.toFixed(7)); document.writeln(Math.PI.toFixed(16)); document.writeln(Math.PI.toFixed( )); // Erzeugt 3 3.14 3.1415927 3.1415926535897930 3
number.toPrecision(genauigkeit) Die toPrecision-Methode wandelt den number-Wert in einen Dezimal-String um. Der optionale Parameter genauigkeit legt die Genauigkeit fest. Er muss zwischen 1 und 21 liegen: document.writeln(Math.PI.toPrecision(2)); document.writeln(Math.PI.toPrecision(7)); document.writeln(Math.PI.toPrecision(16)); document.writeln(Math.PI.toPrecision( ));
Max. Linie 92
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts // Erzeugt 3.1 3.141593 3.141592653589793 3.141592653589793
number.toString(basis) Die Methode toString wandelt den number-Wert in einen String um. Der optionale Parameter basis legt die Basis fest. Er muss zwischen 2 und 36 liegen. Die voreingestellte basis ist Basis 10. Der basis-Parameter wird üblicherweise bei Integerwerten verwendet, kann aber für jede Zahl genutzt werden. Der am weitesten verbreitete Anwendungsfall für number.toString( ) kann auch einfach als String(number) geschrieben werden: document.writeln(Math.PI.toString(2)); document.writeln(Math.PI.toString(8)); document.writeln(Math.PI.toString(16)); document.writeln(Math.PI.toString( )); // Erzeugt 11.001001000011111101101010100010001000010110100011 3.1103755242102643 3.243f6a8885a3 3.141592653589793
Object object.hasOwnProperty(name) Die Methode hasOwnProperty gibt true zurück, wenn object eine Eigenschaft mit diesem name n enthält. Die Prototypkette wird nicht untersucht. Die Methode ist nutzlos, wenn der name hasOwnProperty lautet: var var var var var
a b t u v
= = = = =
{member: true}; Object.create(a); a.hasOwnProperty('member'); b.hasOwnProperty('member'); b.member;
// aus Kapitel 3 // t ist true // u ist false // v ist true
RegEx regex.exec(string)
Max. Linie
Die Methode exec ist die mächtigste (und langsamste) aller Methoden, die reguläre Ausdrücke nutzen. Werden regex und string erfolgreich erkannt, wird ein Array zurückgegeben. Das nullte Element des Arrays enthält den Teil-String, der mit regex übereinstimmt.
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
RegEx
|
93
Max. Linie
Links Das erste Element ist der Text, der durch die erste Gruppe eingefangen wurde, das zweite Element ist der durch die zweite Gruppe eingefangene Text und so weiter. Gibt es keinen Treffer, wird null zurückgegeben. Enthält die regex ein g-Flag, werden die Dinge etwas komplizierter. Die Suche beginnt dann nicht an Position 0 des Strings, sondern an der Position regex.lastIndex (die zu Beginn null ist). Bei einem Treffer wird regex.lastIndex an die Position des ersten Zeichens hinter dem Treffer gesetzt. Gibt es keinen Treffer, wird regex.lastIndex auf 0 zurückgesetzt. Das erlaubt Ihnen die Suche nach mehreren Vorkommen eines Musters in einem String, indem Sie exec in einer Schleife aufrufen. Es gibt eine Reihe von Dingen, auf die man dabei achten muss. Wenn Sie die Schleife früher verlassen, müssen Sie regex.lastIndex selbst auf 0 zurücksetzen, bevor Sie wieder in die Schleife eintreten. Darüber hinaus greift der ^-Faktor nur an regex.lastIndex 0: // // // // // // //
Brich einfachen HTML-Text in Tags und Texte auf (siehe string.replace für die entityify-Methode). Für jedes Tag oder jeden Text erzeugen wir ein Array mit [0] Das vollständige erkannte Tag oder Text [1] Der Tag-Name [2] Das /, wenn es eines gibt [3] Die Attribute, wenn es welche gibt
var text = '
' + 'This is bold<\/b>!<\/p><\/body><\/html>'; var tags = /[^<>]+|<(\/?)([A-Za-z]+)([^<>]*)>/g; var a, i; while ((a = tags.exec(text))) { for (i = 0; i < a.length; i += 1) { document.writeln(('// [' + i + '] ' + a[i]).entityify( )); } document.writeln( ); } // Ergebnis:
Max. Linie 94
// // // //
[0] [1] [2] html [3]
// // // //
[0] [1] [2] body [3] bgcolor=linen
// // // //
[0]
[1] [2] p [3]
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts // // // //
[0] [1] [2] [3]
This is undefined undefined undefined
// // // //
[0] [1] [2] b [3]
// // // //
[0] [1] [2] [3]
// // // //
[0] [1] / [2] b [3]
// // // //
[0] [1] [2] [3]
// // // //
[0]
[1] / [2] p [3]
bold undefined undefined undefined
! undefined undefined undefined
regex.test(string) Die test-Methode ist die einfachste (und schnellste) der Methoden, die reguläre Ausdrücke nutzen. Erkennt die regex den string, wird true zurückgegeben, anderenfalls false. Verwenden Sie bei dieser Methode nicht das g-Flag: var b = /&.+;/.test('frank & beans'); // b is true
test könnte wie folgt implementiert werden: RegEx.method('test', function (string) { return this.exec(string) !== null; });
String string.charAt(pos)
Max. Linie
Die charAt-Methode liefert das Zeichen an Position pos in diesem string zurück. Ist pos kleiner null oder größer oder gleich string.length, wird der leere String zurückgegeben. JavaScript besitzt keinen eigenen Zeichentyp. Das Ergebnis der Methode ist ein String:
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
String
|
95
Max. Linie
Links var name = 'Curly'; var initial = name.charAt(0);
// initial is 'C'
charAt könnte wie folgt implementiert werden: String.method('charAt', function ( pos) { return this.slice(pos, pos + 1); });
string.charCodeAt(pos) Die Methode charCodeAt ist mit charAt identisch, nur dass sie keinen String zurückliefert, sondern die Integer-Darstellung des Zeichens an der Position pos im string. Ist pos kleiner null oder größer oder gleich string.length, gibt sie NaN zurück: var name = 'Curly'; var initial = name.charCodeAt(0);
// initial ist 67
string.concat(string…) Die concat-Methode erzeugt einen neuen String, indem sie andere Strings verkettet. Sie wird selten verwendet, da der Operator + praktischer ist: var s = 'C'.concat('a', 't');
// s ist 'Cat'
string.indexOf(suchString, position) Die indexOf-Methode sucht nach einem suchString innerhalb des strings. Wird dieser gefunden, gibt sie die Position des ersten Zeichens zurück, anderenfalls –1. Der optionale position-Parameter lässt die Suche an der angegebenen Position innerhalb des strings beginnen: var var p = p =
text = 'Mississippi'; p = text.indexOf('ss'); text.indexOf('ss', 3); text.indexOf('ss', 6);
// p ist 2 // p ist 5 // p ist -1
string.lastIndexOf(suchString, position) Die lastIndexOf-Methode ähnelt der indexOf-Methode, nur dass die Suche am Ende des Strings beginnt: var var p = p =
text = 'Mississippi'; p = text.lastIndexOf('ss'); text.lastIndexOf('ss', 3); text.lastIndexOf('ss', 6);
// p ist 5 // p ist 2 // p ist 5
string.localeCompare(that) Die localCompare-Methode vergleicht zwei Strings. Die Regeln für diesen String-Vergleich sind nicht spezifiziert. Ist dieser string kleiner als der that -String, ist das Ergebnis negativ. Sind sie gleich, ist das Ergebnis null. Das entspricht der Konvention der Vergleichsfunktion für array.sort: var m = ['AAA', 'A', 'aa', 'a', 'Aa', 'aaa']; m.sort(function (a, b) {
Max. Linie 96
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts return a.localeCompare(b); }); // m (in irgendeinem locale) ist // ['a', 'A', 'aa', 'Aa', 'aaa', 'AAA']
string.match(regex) Die match-Methode »matcht« einen String und einen regulären Ausdruck. Wie dieses Matching erfolgt, hängt vom g-Flag ab. Ist kein g-Flag angegeben, entspricht der Ergebnis des Aufrufs von string.match(regex) dem des Aufrufs von regex.exec(string). Enthält regex aber das g-Flag, dann wird ein Array aller Treffer erzeugt, bei dem aber die einfangenden Gruppen fehlen: var text = '
' + 'This is bold<\/b>!<\/p><\/body><\/html>'; var tags = /[^<>]+|<(\/?)([A-Za-z]+)([^<>]*)>/g; var a, i; a = text.match(tags); for (i = 0; i < a.length; i += 1) { document.writeln(('// [' + i + '] ' + a[i]).entityify( )); } // The result is // // // // // // // // // // //
[0] [1] [2]
[3] This is [4] [5] bold [6] [7] ! [8]
[9] [10]
string.replace(suchWert, ersetzungsWert) Die replace-Methode führt eine Suchen/Ersetzen-Operation über den string durch und gibt einen neuen String zurück. Das suchWert-Argument kann ein String oder ein RegexObjekt sein. Ist er ein String, wird nur das erste Vorkommen des suchWerts ersetzt. Das heißt, var result = "mother_in_law".replace('_', '-');
erzeugt "mother-in_law", was Sie möglicherweise nicht wollen. Ist der suchWert ein regulärer Ausdruck und enthält dieser das g-Flag, dann werden alle Vorkommen ersetzt. Ist das g-Flag nicht vorhanden, wird nur das erste Vorkommen ersetzt.
Max. Linie
Der ersetzungsWert kann ein String oder eine Funktion sein. Ist der ersetzungsWert ein String, hat das $-Zeichen eine besondere Bedeutung:
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
String
|
97
Max. Linie
Links // Halte 3 Ziffern in Klammern fest var oldareacode = /\((\d{3})\)/g; var p = '(555)666-1212'.replace(oldareacode, '$1-'); // p ist '555-555-1212'
DollarzeichenSequenz
Ersetzung
$$
$
$&
Der erkannte Text
$zahl
Text der einfangenden Gruppe
$`
Der vor dem Match stehende Text
$'
Der auf den Match folgende Text
Ist der ersetzungsWert eine Funktion, wird diese für jeden Treffer aufgerufen, und der von dieser Funktion zurückgelieferte String wird als Ersetzungstext verwendet. Der erste an die Funktion übergebene Parameter ist der gematchte Text. Der zweite Parameter ist der Text der einfangenden Gruppe 1, der nächste Parameter ist der Text der einfangenden Gruppe 2 und so weiter: String.method('entityify', function ( ) { var character = { '<' : '<', '>' : '>', '&' : '&', '"' : '"' }; // // // // // //
Gib die string.entityify-Methode zurück, die das Ergebnis des Aufrufs der Ersetzungsmethode zurückgibt. Deren ersetzungsWert-Funktion gibt das Ergebnis eines Lookups eines Zeichens in einem Objekt zurück. Diese Nutzung eines Objekts ist üblicherweise schneller als Switch-Anweisungen. return function ( ) { return this.replace(/[<>&"]/g, function (c) { return character[c]; }); }; }( )); alert("<&>".entityify( )); // <&>
string.search(regex)
Max. Linie
Die search-Methode ähnelt der indexOf-Methode, verwendet aber ein Regex-Objekt anstelle eines Strings. Sie liefert die Position des ersten Zeichens des ersten Treffers zurück, wenn es einen gibt, oder –1, wenn die Suche fehlschlägt. Das g-Flag wird ignoriert. Es gibt keinen positions-Parameter:
98
|
Kapitel 8: Methoden
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts var text = 'and in it he says "Any damn fool could'; var pos = text.search(/["']/); // pos ist 18
string.slice(anfang, ende) Die slice-Methode erzeugt einen neuen String, indem sie einen Teil eines anderen strings kopiert. Ist der anfang-Parameter negativ, wird zu ihm string.length hinzuaddiert. Der ende-Parameter ist optional und ist standardmäßig auf string.length gesetzt. Ist der ende-Parameter negativ, wird ihm string.length hinzuaddiert. Der ende-Parameter ist um eine Position größer als die Position des letzten Zeichens. Um n Zeichen ab der Position p zu kopieren, verwenden Sie string.slice(p, p + n). Siehe auch string. substring und array.slice an anderer Stelle in diesem Kapitel. var text = 'and in it he says "Any damn fool could'; var a = text.slice(18); // a is '"Any damn fool could' var b = text.slice(0, 3); // b ist 'and' var c = text.slice(-5); // c ist 'could' var d = text.slice(19, 32); // d ist 'Any damn fool'
string.split(separator, limit) Die split-Methode erzeugt ein Array aus Strings, indem es den string in einzelne Teile zerlegt. Der optionale limit-Parameter beschränkt dabei die Anzahl dieser Teile. Der separator -Parameter kann ein String oder ein regulärer Ausdruck sein. Ist der separator ein leerer String, wird ein Array einzelner Zeichen erzeugt: var digits = '0123456789'; var a = digits.split('', 5); // a ist ['0', '1', '2', '3', '456789']
Anderenfalls wird der string nach allen Vorkommen des separators abgesucht. Jedes Textelement zwischen den Separatoren wird in das Array kopiert. Das g-Flag wird ignoriert: var ip = '192.168.1.0'; var b = ip.split('.'); // b ist ['192', '168', '1', '0'] var c = '|a|b|c|'.split('|'); // c ist ['', 'a', 'b', 'c', '']
Max. Linie
var text = 'last, first ,middle'; var d = text.split(/\s*,\s*/); // d ist [ // 'last', // 'first', // 'middle' // ]
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie String
|
99
Links Es gibt einige Sonderfälle, auf die Sie achten müssen. Text aus einfangenden Gruppen wird in den split eingebunden: var e = text.split(/\s*(,)\s*/); // e ist [ // 'last', // ',', // 'first', // ',', // 'middle' // ]
Einige Implementierungen unterdrücken leere Strings im Ausgabe-Array, wenn der separator ein regulärer Ausdruck ist: var f = '|a|b|c|'.split(/\|/); // f ist bei einigen System ['a', 'b', 'c'] // und bei anderen ['', 'a', 'b', 'c', '']
string.substring(anfang, ende) Die substring -Methode entspricht der slice-Methode, passt negative Parameter aber nicht an. Es gibt keinen Grund für die Verwendung der substring-Methode. Verwenden Sie stattdessen die slice-Methode.
string.toLocaleLowerCase( ) Die toLocaleLowerCase-Methode erzeugt einen neuen String, indem sie den string entsprechend den Regeln des Locales in Kleinbuchstaben umwandelt. Das kommt hauptsächlich im Türkischen zum Tragen, da in dieser Sprache »I« zu » i« wird, nicht zu »i«.
string.toLocaleUpperCase( ) Die toLocaleUpperCase-Methode erzeugt einen neuen String, indem sie den string entsprechend den Regeln des Locales in Großbuchstaben umwandelt. Dies kommt hauptsächlich im Türkischen zum Tragen, da in dieser Sprache »i« zu » « wird, nicht zu »I«.
string.toLowerCase( ) Die toLowerCase-Methode erzeugt einen neuen String, indem sie den string in Kleinbuchstaben umwandelt.
string.toUpperCase( ) Die toUpperCase-Methode erzeugt einen neuen String, indem sie den string in Großbuchstaben umwandelt.
String.fromCharCode(zeichen…) Die String.fromCharCode-Funktion erzeugt einen String aus einer Reihe von Zahlen. var a = String.fromCharCode(67, 97, 116); // a ist 'Cat'
Max. Linie
Max. Linie 100
|
Kapitel 8: Methoden This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 9
KAPITEL 9
Stil
Here is a silly stately style indeed! – William Shakespeare, The First Part of Henry the Sixth
Computerprogramme sind die komplexesten von Menschen hergestellten Dinge. Programme bestehen aus einer riesigen Anzahl von Teilen, ausgedrückt in Funktionen, Anweisungen oder Ausdrücken, die in Sequenzen angeordnet werden und nahezu fehlerfrei sein müssen. Das Laufzeitverhalten hat nur wenig Ähnlichkeit mit dem Programm, das es implementiert. Man geht üblicherweise davon aus, dass Software während ihres gesamten produktiven Lebens verändert wird. Der Prozess, ein korrektes Programm in ein anderes korrektes Programm zu überführen, stellt eine extreme Herausforderung dar. Gute Programme besitzen eine Struktur, die mögliche zukünftige Veränderungen unterstützt, ohne dabei zu einer Last zu werden. Gute Programme besitzen außerdem eine klare Darstellung. Wenn sich ein Programm klar ausdrückt, haben wir die besten Chancen, es zu verstehen, und können es erfolgreich modifizieren oder reparieren. Dies gilt für alle Programmiersprachen, aber es gilt ganz besonders für JavaScript. Die schwache Typisierung und weitgehende Fehlertoleranz von JavaScript bietet uns bei der Kompilierung nur wenig Sicherheit in Bezug auf die Qualität unserer Programme. Um das zu kompensieren, müssen wir sehr diszipliniert kodieren. JavaScript enthält eine große Anzahl schwacher oder problematischer Features, die unseren Versuch unterwandern können, gute Programme zu schreiben. Ganz offensichtlich müssen wir die schlechtesten Features von JavaScript meiden. Vielleicht etwas überraschend ist die Tatsache, dass man auch die Features meiden sollte, die oft nützlich, gelegentlich aber auch gefährlich sind. Solche Features sind attraktive Plagegeister, und indem man sie meidet, wird eine große Gruppe potenzieller Fehler vermieden.
Max. Linie
Der langfristige Wert einer Software für eine Organisation steht im direkten Verhältnis zur Qualität der Codebasis. Über seine gesamte Lebensdauer hinweg wird
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
101
Max. Linie
Links ein Programm von vielen Menschen bearbeitet. Ist ein Programm in der Lage, seine Struktur und Charakteristik deutlich zu kommunizieren, ist es weniger wahrscheinlich, dass es bei einer Modifikation in nicht ganz so ferner Zukunft zusammenbricht. JavaScript-Code wird häufig direkt veröffentlicht. Er sollte daher immer die entsprechend hohe Qualität haben. Sauberkeit zählt. Wenn Sie in einem sauberen und konsistenten Stil schreiben, werden Ihre Programme einfacher zu lesen. Programmierer können endlos darüber debattieren, was guten Stil ausmacht. Die meisten Programmierer halten strikt an dem fest, was sie gewohnt sind, oft an dem Stil, den sie während ihrer Ausbildung oder ihrem ersten Job kennengelernt haben. Einige haben auch ohne den geringsten Hauch von Stil erfolgreiche Karrieren hinter sich. Ist das nicht ein Beweis dafür, dass Stil keine Rolle spielt? Und selbst wenn der Stil keine Rolle spielt, ist nicht ein Stil so gut wie der andere? Es stellt sich heraus, dass der Stil beim Programmieren aus den gleichen Gründen wichtig ist wie beim Schreiben. Er sorgt für bessere Lesbarkeit. Computerprogramme werden manchmal als reines Schreibmedium betrachtet, so dass es kaum eine Rolle spielt, wie etwas geschrieben ist, solange es nur richtig funktioniert. Es stellt sich aber heraus, dass die Wahrscheinlichkeit, dass ein Programm richtig funktioniert, deutlich erhöht wird, wenn man es lesen kann, was gleichzeitig auch die Wahrscheinlichkeit erhöht, dass es wie erwartet funktioniert. Es liegt auch in der Natur von Software, dass sie über ihre gesamte Lebensdauer häufig modifiziert wird. Wenn wir sie lesen und verstehen können, dürfen wir auch hoffen, sie modifizieren und verbessern zu können. In diesem Buch verwende ich durchgehend einen konsistenten Stil. Meine Absicht war, die Codebeispiele so lesbar wie möglich zu machen. Ich habe Whitespace konsistent eingesetzt, um Ihnen zusätzliche Hinweise zur Bedeutung meiner Programme zu geben. Ich habe die Inhalte von Blöcken und Objektliteralen mit vier Leerzeichen eingerückt. Ich habe ein Leerzeichen zwischen if und ( eingefügt, damit das if nicht wie ein Funktionsaufruf aussieht. Nur in Aufrufen habe ich ( direkt neben dem vorstehenden Symbol stehen lassen. Ich habe Leerzeichen um alle Infix-Operatoren gestellt, außer um . und [, da diese einen höheren Vorrang haben. Ich habe ein Leerzeichen hinter jedem Komma und jedem Doppelpunkt verwendet.
Max. Linie
Ich verwende nur eine Anweisung pro Zeile. Mehrere Anweisungen in einer Zeile können schnell falsch interpretiert werden. Passt eine Anweisung nicht in eine einzelne Zeile, umbreche ich sie nach einem Komma oder einem binären Operator. Das bietet einen besseren Schutz vor Copy-and-Paste-Fehlern, die durch das Einfügen eines Semikolons getarnt werden. (Das Tragische am Einfügen eines Semikolons wird in Anhang A deutlich.) Ich rücke den Rest der Anweisung um vier zusätz-
102
|
Kapitel 9: Stil
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts liche Leerzeichen ein – oder um acht Leerzeichen, wenn vier Leerzeichen irreführend wären (etwa bei einem Zeilenumbruch im Bedingungsteil einer ifAnweisung). Ich verwende immer Blöcke bei strukturierten Anweisungen wie if und while, da das weniger fehleranfällig ist. Mir ist schon untergekommen, dass if (a) b( );
zu if (a) b( ); c( );
wurde, was ein sehr schwer zu erkennender Fehler ist. Er sieht aus wie if (a) { b( ); c( ); }
bedeutet aber: if (a) { b( ); } c( );
Code, der eine Sache zu bedeuten scheint, tatsächlich aber etwas anderes bedeutet, kann Bugs verursachen. Ein Paar geschweifter Klammern ist ein recht billiger Schutz vor Fehlern, die zu finden einen recht teuer zu stehen kommen kann. Ich verwende immer den K&R-Stil, bei dem das { immer am Ende der Zeile steht statt am Anfang, da er einen furchtbaren Designfehler in der return-Anweisung von JavaScript vermeidet. Ich habe einige Kommentare eingefügt. Ich hinterlasse in meinen Programmen gern Kommentare, die später von der Person gelesen werden (möglicherweise von mir selbst), die verstehen muss, was ich so gedacht habe. Machmal stelle ich mir Kommentare als eine Art Zeitmaschine vor, mit der ich mir wichtige Nachrichten in die Zukunft schicke. Ich bemühe mich, die Kommentare auf dem neuesten Stand zu halten. Fehlerhafte Kommentare können es noch schwerer machen, ein Programm richtig zu lesen und zu verstehen. Ich kann das nicht gebrauchen. Ich habe versucht, Ihre Zeit nicht mit sinnlosen Kommentaren wie dem folgenden zu verschwenden:
Max. Linie
i = 0; // Setze i auf null.
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Stil
|
103
Links In JavaScript bevorzuge ich einzeilige Kommentare. Block-Kommentare sind für die formale Dokumentation und für das Auskommentieren reserviert. Ich ziehe es vor, die Struktur meiner Programme selbsterklärend zu gestalten, was Kommentare überflüssig macht. Ich bin damit nicht immer erfolgreich, so dass ich Kommentare einfüge, während meine Programme auf ihre Perfektionierung warten. JavaScript besitzt eine C-Syntax, aber die Blöcke haben keinen Geltungsbereich. Daher ist die Konvention, Variablen bei ihrer ersten Verwendung zu deklarieren, für JavaScript ein wirklich schlechter Rat. JavaScript besitzt einen Funktions-, keinen Block-Geltungsbereich, weshalb ich meine Variablen am Anfang jeder Funktion deklariere. JavaScript erlaubt die Deklaration von Variablen, nachdem sie genutzt wurden. Das fühlt sich für mich wie ein Fehler an, und ich möchte keine Programme schreiben, die wie ein Fehler aussehen. Ich möchte, dass meine Fehler herausragen. Ebenso verwende ich niemals einen Zuweisungsausdruck im Bedingungsteil eines if, weil mit if (a = b) { ... }
wahrscheinlich Folgendes beabsichtigt war: if (a === b) { ... }
Ich möchte Idiome vermeiden, die nach Fehlern aussehen. Ich erlaube es switch-Anweisungen niemals, in den nächsten Fall durchzurutschen. Ich habe einmal einen Bug in meinem Code gefunden, der durch ein unbeabsichtigtes Durchrutschen verursacht wurde, kurz nachdem ich eine heftige Diskussion darüber hatte, wie nützlich ein solches Durchrutschen manchmal sein kann. Ich war immerhin schlau genug, aus dieser Erfahrung zu lernen. Wenn ich die Features einer Sprache untersuche, achte ich nun besonders auf die Features, die manchmal nützlich sind, gelegentlich aber auch gefährlich. Diese stellen die schlimmste Seite von JavaScript dar, da man nur schwer sagen kann, ob sie richtig verwendet wurden. Das ist die Stelle, an der sich die Bugs verstecken. Qualität war kein motivierender Aspekt beim Design, der Implementierung und der Standardisierung von JavaScript. Das bürdet den Benutzern der Sprache eine größere Last auf, den Schwächen der Sprache zu widerstehen. JavaScript unterstützt die Entwicklung großer Programme, besitzt aber auch Gebilde und Idiome, die große Programme sabotieren. Zum Beispiel bietet JavaScript einige Annehmlichkeiten bei der Verwendung globaler Variablen, aber globale Variablen werden immer problematischer, je komplexer ein Programm wird.
Max. Linie
Ich verwende eine einzelne globale Variable für eine Anwendung oder Bibliothek. Jedes Objekt besitzt seinen eigenen Namensraum, so dass es einfach ist, Objekte zu nutzen, um meinen Code zu organisieren. Die Verwendung von Closures ermöglicht ein zusätzliches Information Hiding, was die Stärke meiner Module erhöht.
104
|
Kapitel 9: Stil
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
First
Kapitel 10
KAPITEL 10
Schöne Features
Thus, expecting thy reply, I profane my lips on thy foot, my eyes on thy picture, and my heart on thy every part. Thine, in the dearest design of industry… – William Shakespeare, Love’s Labor’s Lost
Letztes Jahr wurde ich eingeladen, ein Kapitel zu Andy Orams und Greg Wilsons Beautiful Code (O’Reilly) beizusteuern, einer Anthologie zum Thema Schönheit, wie sie sich in Computerprogrammen darstellt. Ich wollte mein Kapitel in JavaScript schreiben. Ich wollte etwas Abstraktes, Leistungsfähiges und Nützliches präsentieren, um zu zeigen, dass die Sprache dazu fähig ist. Und ich wollte den Browser und alle anderen Bereiche auslassen, in denen JavaScript vorherrscht. Ich wollte etwas Respektables zeigen, etwas von Gewicht. Ich dachte sofort an Vaughn Pratts Top-Down-Operator-Precedence-Parser, den ich in JSLint (siehe Anhang C nutze). Parsing ist in der Computertechnik ein wichtiges Thema. Die Fähigkeit, einen Compiler für eine Sprache in der Sprache selbst zu schreiben, ist gleichzeitig ein Test für die Vollständigkeit der Sprache. Ich wollte den gesamten Code für einen in JavaScript geschriebenen Parser für JavaScript aufnehmen. Aber mein Kapitel war nur eines von 30 oder 40, so dass mir nur eine beschränkte Anzahl von Seiten zur Verfügung stand. Erschwert wurde die Sache noch dadurch, dass die meisten Leser keine Erfahrung mit JavaScript haben würden, d.h., ich würde auch die Sprache und ihre Eigenheiten vorstellen müssen. Ich entschied mich daher für eine Untermenge der Sprache. Auf diese Weise musste ich nicht die gesamte Sprache parsen, und ich musste nicht die gesamte Sprache beschreiben. Ich nannte diese Untermenge Simplified JavaScript. Die Wahl der Untermenge war einfach: Ich nahm nur die Features auf, die ich zur Entwicklung des Parsers benötigte. In Beautiful Code beschrieb ich das wie folgt: Simplified JavaScript enthält nur die guten Seiten der Sprache einschließlich:
Max. Linie
Funktionen als erste Klassenobjekte Funktionen in Simplified JavaScript sind Lambdas mit lexikalischem Geltungsbereich.
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
105
Max. Linie
Links Dynamische Objekte mit prototypischer Vererbung Objekte sind klassenfrei. Wird können jedes Objekt durch eine einfache Zuweisung um neue Member erweitern. Ein Objekt kann Member von anderen Objekten erben. Objektliterale und Array-Literale Das ist eine sehr praktische Notation zum Aufbau neuer Objekte und Arrays. JavaScript-Literale waren die Vorlage für das Datenaustauschformat JSON.
Die Untermenge enthielt das Beste der guten Seite. Obwohl es sich um eine kleine Sprache handelte, war sie sehr ausdrucksstark und leistungsfähig. JavaScript besitzt viele zusätzliche Features, die eigentlich nicht sehr viel bringen, und wie Sie in den folgenden Anhängen noch sehen werden, gibt es viele Features mit negativen Auswirkungen. In der Untermenge finden Sie nichts Hässliches oder Schlechtes; all das wurde weggelassen. Simplified JavaScript ist streng genommen keine Untermenge. Ich habe einige wenige neue Features aufgenommen. Das einfachste war, pi als einfache Konstante einzufügen. Ich tat das, um ein Feature des Parsers zu demonstrieren. Ich habe auch bessere Regeln für den Umgang mit reservierten Wörtern vorgestellt und gezeigt, dass reservierte Wörter unnötig sind. In einer Funktion kann ein Wort nicht sowohl für eine Variable als auch für einen Parameternamen oder ein Sprachfeature verwendet werden. Sie können ein Wort für das eine oder das andere verwenden, und der Programmierer hat die Wahl. Das erleichtert das Erlernen der Sprache, weil man nicht auf Features achten muss, die man nicht nutzt. Darüber hinaus kann die Sprache einfacher erweitert werden, weil es nicht notwendig ist, weitere Wörter zu reservieren, um neue Features hinzuzufügen. Ich habe auch den Block-Geltungsbereich eingeführt. Der Block-Geltungsbereich ist kein unabdingbares Feature; ihn nicht zu besitzen verwirrt aber erfahrene Programmierer. Ich habe das Block-Scoping aufgenommen, weil ich davon ausgegangen bin, dass mein Parser auch zur Verarbeitung von anderen Sprachen als JavaScript verwendet werden würde, und diese Sprachen würden dann ein korrektes Scoping durchführen. Der von mir entwickelte Parser ist in einem Stil geschrieben, der sich nicht darum kümmert, ob ein Block-Geltungsbereich verfügbar ist oder nicht. Ich empfehle Ihnen, das auf die gleiche Weise zu handhaben. Als ich damit begann, über dieses Buch nachzudenken, wollte ich die Idee mit der Untermenge weiterführen, um zu zeigen, wie man eine vorhandene Sprache signifikant verbessern kann, ohne irgendwelche Änderungen vorzunehmen, sondern einfach, indem man unnütze Features weglässt.
Max. Linie
Man sieht sehr viel Feature-gesteuertes Produktdesign, bei dem der Preis der Features nicht richtig berücksichtigt wird. Features können sich für den Anwender negativ auswirken, weil sie es schwerer machen, ein Produkt zu verstehen und zu nutzen. Es zeigt sich, dass die Anwender diejenigen Produkte mögen, die einfach nur funktionieren. Es stellt sich aber heraus, dass einfach nur funktionierende Designs wesentlich schwerer zu entwickeln sind als Designs, die lange Feature-Listen aneinanderreihen. 106
|
Kapitel 10: Schöne Features This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Features verursachen Kosten: Spezifikationskosten, Designkosten und Entwicklungskosten. Es gibt Test- und Zuverlässigkeitskosten. Je mehr Features es gibt, desto wahrscheinlicher entwickeln sich Probleme oder die Features spielen immer schlechter zusammen. Bei Softwaresystemen gibt es Speicherkosten, die man zwischenzeitlich vernachlässigen konnte, aber durch das Aufkommen mobiler Anwendungen gewinnt dieser Faktor wieder an Bedeutung. Man zahlt einen höheren Preis für Performance, weil Moores Gesetz für Batterien nicht gilt. Features verursachen Dokumentationskosten. Jedes Feature führt zu zusätzlichen Seiten im Handbuch und erhöht die Trainingskosten. Features, die nur wenigen Benutzern zugute kommen, verursachen Kosten für alle Benutzer. Beim Entwurf von Produkten und Programmiersprachen wollen wir uns also auf die Kern-Features – kurz: die guten Seiten – konzentrieren, weil wir an dieser Stelle den größten Wert erzeugen. Wir finden alle die guten Seiten in den von uns verwendeten Produkten. Wir schätzen Einfachheit, und wenn man uns diese nicht bietet, schaffen wir sie uns selbst. Meine Mikrowelle besitzt Unmengen an Features, aber ich verwende nur das Erhitzen und die Uhr. Und das Stellen der Uhr ist mühsam. Wir begegnen der Komplexität Feature-gesteuerter Produkte, indem wir die guten Seiten aufdecken und an ihnen festhalten. Es wäre schön, wenn Produkte und Programmiersprachen so entworfen werden würden, dass sie nur die guten Seiten enthielten.
Max. Linie
Max. Linie Schöne Features This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
107
Anhang A A ANHANG
Furchtbare Seiten
That will prove awful both in deed and word. – William Shakespeare, Pericles, Prince of Tyre
In diesem Kapitel stelle ich die problematischen Features von JavaScript vor, die sich nicht so leicht vermeiden lassen. Sie müssen sich dieser Dinge bewusst sein und bereit sein, gegen sie anzukämpfen.
Globale Variablen Das schlimmste aller schlechten Features von JavaScript ist dessen Abhängigkeit von globalen Variablen. Eine globale Variable ist eine Variable, die in jedem Geltungsbereich sichtbar ist. Globale Variablen können in sehr kleinen Programmen recht praktisch sein, werden aber schnell unhandlich, wenn die Programme größer werden. Da eine globale Variable von jedem Teil eines Programms zu jeder Zeit verändert werden kann, können globale Variablen das Verhalten des Programms erheblich verkomplizieren. Die Verwendung globaler Variablen verringert die Zuverlässigkeit der Programme, die sie nutzen. Globale Variablen erschweren die Ausführung unabhängiger Unterprogramme innerhalb des gleichen Programms. Teilen sich Unterprogramme globale Variablen gleichen Namens, stören sie einander, und es kommt zu Fehlern, die üblicherweise nur schwer zu finden sind. Viele Sprache, kennen globale Variablen. Zum Beispiel sind Javas public staticMember globale Variablen. Das Problem mit JavaScript ist nicht nur, dass die Sprache sie erlaubt, sondern dass sie sie braucht. JavaScript besitzt keinen Linker. Alle Kompilierungseinheiten werden in ein gemeinsames globales Objekt geladen. Es gibt drei Möglichkeiten, globale Variablen zu definieren. Die erste besteht darin, eine var-Anweisung außerhalb einer Funktion anzugeben:
Max. Linie
var foo = value;
108
|
Anhang A: Furchtbare Seiten
Max. Linie
Rechts Die zweite fügt eine Eigenschaft direkt in das globale Objekt ein. Das globale Objekt ist der Container für alle globalen Variablen. In Webbrowsern läuft das globale Objekt unter dem Namen window: window.foo = value;
Die dritte Methode besteht darin, eine Variable einfach zu verwenden, ohne sie zu deklarieren. Das wird implizierte globale Variable genannt: foo = value;
Gedacht war das als Erleichterung für Anfänger, da sie es unnötig machte, Variablen zu deklarieren, bevor man sie nutzte. Leider ist es ein sehr weit verbreiteter Fehler, die Deklaration von Variablen zu vergessen. JavaScripts Verhalten, vergessene Variablen zu globalen Variablen zu machen, führt zu Bugs, die nur sehr schwer zu finden sind.
Geltungsbereich Die Syntax von JavaScript stammt von C ab. In allen anderen C-ähnlichen Sprachen erzeugt ein Block (eine Gruppe von Anweisungen, die in geschweiften Klammern stehen) einen Geltungsbereich. Innerhalb eines Blocks deklarierte Variablen sind außerhalb des Blocks nicht sichtbar. JavaScript nutzt diese Block-Syntax, bietet aber keinen Block-Geltungsbereich: Eine Variable, die in einem Block deklariert wurde, ist innerhalb der gesamten Funktion sichtbar, die den Block enthält. Das kann für Programmierer, die Erfahrung mit anderen Sprachen haben, recht überraschend sein. In den meisten Sprachen ist es üblicherweise am besten, Variablen an der Stelle zu deklarieren, an der sie zum ersten Mal verwendet werden. Das ist bei JavaScript kein guter Ansatz, weil die Sprache keinen Block-Geltungsbereich besitzt. Es ist daher besser, alle Variablen zu Beginn jeder Funktion zu deklarieren.
Semikolon einfügen JavaScript besitzt einen Mechanismus, der versucht, fehlerhafte Programme zu korrigieren, indem er automatisch Semikola einfügt. Verlassen Sie sich nicht auf diesen Mechanismus. Er kann ernste Fehler verschleiern. Er fügt manchmal Semikola an Stellen ein, an denen sie nicht erwünscht sind. Sehen Sie sich die Konsequenzen des Einfügens von Semikola bei der return-Anweisung an. Gibt eine return-Anweisung einen Wert zurück, muss der entsprechende Ausdruck in der gleichen Zeile liegen wie das return:
Max. Linie
return { status: true };
Semikolon einfügen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
109
Links Das scheint ein Objekt zurückzugeben, das ein status-Member enthält. Unglücklicherweise macht das eingefügte Semikolon daraus eine Anweisung, die undefined zurückgibt. Es gibt keinen Warnhinweis, dass das eingefügte Semikolon die Fehlinterpretation des Programms verursacht hat. Man kann das Problem vermeiden, indem man das { am Ende der vorherigen Zeile platziert und nicht am Anfang der nächsten Zeile: return { status: true };
Reservierte Wörter Die folgenden Wörter sind in JavaScript reserviert: abstract boolean break byte case catch char class const continue debugger default delete do double else enum export extends false final finally float for function goto if implements import in instanceof int interface long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var volatile void while with
Die meisten dieser Wörter werden von der Sprache nicht verwendet. Diese Wörter können nicht als Variablennamen oder Parameter genutzt werden. Werden reservierte Wörter als Schlüssel in Objektliteralen verwendet, müssen sie in Anführungszeichen stehen. Sie können nicht mit der Punktnotation verwendet werden, weshalb man manchmal mit eckigen Klammern arbeiten muss: var method; var class; object = {box: value}; object = {case: value}; object = {'case': value}; object.box = value; object.case = value; object['case'] = value;
// // // // // // // //
o.k. unzulässig o.k. unzulässig o.k. o.k. unzulässig o.k.
Unicode JavaScript wurde zu einer Zeit entworfen, in der man davon ausging, dass Unicode maximal 65.536 Zeichen haben würde. Seitdem ist es auf über eine Million Zeichen angewachsen.
Max. Linie
In JavaScript sind Zeichen 16 Bit breit. Das reicht, um die ursprünglichen 65.536 Zeichen (die sogenannte Basic Multilingual Plane) abzudecken. Jedes der restlichen Million Zeichen kann als ein Paar von Zeichen dargestellt werden. Unicode betrachtet dieses Paar als ein einzelnes Zeichen. Für JavaScript stellt dieses Paar zwei verschiedene Zeichen dar.
110
|
Anhang A: Furchtbare Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts typeof Der typeof-Operator liefert einen String zurück, der den Typ seines Operanden identifiziert. So liefert typeof 98.6
'number' zurück. Unglücklicherweise gibt typeof null
'object' statt 'null' zurück. Hoppla. Ein besser Test auf null ist einfach: my_value === null
Ein größeres Problem ist der Test eines Wertes daraufhin, ob es sich um ein Objekt handelt. typeof kann nicht zwischen null und Objekten unterscheiden, Sie können das aber schon, weil null falsch ist und alle Objekte wahr sind: if (my_value && typeof my_value === 'object') { // my_value ist ein Objekt oder ein Array! }
Siehe auch die noch folgenden Abschnitte »NaN« und »Falsche Arrays«. Die verschiedenen Implementierungen sind in Bezug auf das Regex-Objekt nicht einheitlich. Einige Implementierungen betrachten typeof /a/
als 'object', während andere das als 'function' sehen. Es wäre wohl besser gewesen, das als 'regex' zu sehen, aber der Standard erlaubt das nicht.
parseInt parseInt ist eine Funktion, die einen String in einen Integerwert umwandelt. Die Funktion bricht bei der ersten Nicht-Ziffer ab, d.h., parseInt("16") und parseInt("16 tons") liefern das gleiche Ergebnis zurück. Es wäre schön, wenn uns die Funktion irgendwie über den zusätzlichen Text informieren würde, aber das macht sie nicht.
Ist das erste Zeichen des Strings eine 0, dann wird der String zur Basis 8 evaluiert statt zur Basis 10. Für Basis 8 sind 8 und 9 keine Ziffern, weshalb parseInt("08") und parseInt("09") eine 0 als Ergebnis zurückliefern. Dieser Fehler kann Probleme in Programmen verursachen, die Datum und Uhrzeit verarbeiten. Glücklicherweise kann man parseInt einen Basis-Parameter übergeben, so dass parseInt("08", 10) auch wirklich 8 zurückgibt. Ich empfehle Ihnen, die Basis immer mit anzugeben.
Max. Linie
Max. Linie parseInt This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
111
Links + Der Operator + kann addieren oder verketten. Was genau passiert, hängt von den Parametertypen ab. Ist einer der beiden Operanden ein Leer-String, wird der andere Operand in einen String umgewandelt und zurückgegeben. Sind beide Operanden Zahlen, wird deren Summe zurückgegeben. Anderenfalls wandelt es beide Operanden in Strings um und verkettet sie. Dieses komplizierte Verhalten ist eine häufige Fehlerquelle. Soll + addieren, müssen Sie sicherstellen, dass beide Operanden Zahlen sind.
Fließkomma Binäre Fließkommazahlen sind für die Verarbeitung von Dezimalbrüchen ungeeignet, d.h., 0.1 + 0.2 ist nicht gleich 0.3. Das ist der am häufigsten gemeldete Bug bei JavaScript und die folgerichtige Konsequenz aus der Übernahme des IEEE-Standards für binäre Fließkomma-Arithmetik (IEEE 754). Dieser Standard ist für viele Anwendungen gut geeignet, verstößt aber gegen das meiste, was Sie in der Schule über Zahlen gelernt haben. Glücklicherweise funktioniert die Integer-Arithmetik mit Fließkommazahlen korrekt, so dass Fehler in der Dezimaldarstellung durch Skalierung vermieden werden können. Zum Beispiel können Euro-Werte in ganze Cent-Werte umgewandelt werden, indem man sie mit 100 multipliziert. Die Cents können dann korrekt addiert werden. Die Summe kann dann wieder, durch 100 geteilt, in Euro umgewandelt werden. Die Anwender einer Anwendung haben eine berechtigte Erwartungshaltung, dass die Ergebnisse bei Geldberechnungen korrekt sind.
NaN Der Wert NaN ist ein durch IEEE 754 definierter Spezialwert. Er steht für Not a Number (also »keine Zahl«), obwohl Folgendes gilt: typeof NaN === 'number'
// true
Sie können diesen Wert erzeugen, wenn Sie versuchen, einen String, der keine Zahl darstellt, in eine Zahl umzuwandeln. Hier ein Beispiel: + '0' + 'hoppla'
// 0 // NaN
Ist NaN Operand einer arithmetischen Operation, dann ist das Ergebnis NaN. Erzeugt eine Kette von Formeln also das Ergebnis NaN, dann war zumindest eine der Eingaben NaN, oder NaN wurde irgendwo erzeugt.
Max. Linie
Max. Linie 112
|
Anhang A: Furchtbare Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Sie können auf NaN testen. Wie wir gesehen haben, kann typeof nicht zwischen Zahlen und NaN unterscheiden, und wie sich zeigt, ist NaN nicht gleich zu sich selbst. Überraschenderweise gilt also Folgendes: NaN === NaN NaN !== NaN
// false // true
JavaScript stellt eine isNaN-Funktion bereit, die zwischen Zahlen und NaN unterscheiden kann: isNaN(NaN) isNaN(0) isNaN('oops') isNaN('0')
// // // //
true false true false
Die Funktion isFinite bietet die beste Möglichkeit, um zu erkennen, ob ein Wert als Zahl verwendet werden kann, da sie NaN und Infinity ablehnt. Leider versucht isFinite, ihre Operanden in eine Zahl umzuwandeln, und ist daher kein guter Test, ob ein Wert tatsächlich eine Zahl ist. Sie werden daher eine eigene isNumber-Funktion definieren wollen: var isNumber = function isNumber(value) { return typeof value === 'number' && isFinite(value); }
Falsche Arrays JavaScript besitzt keine echten Arrays. Das ist nicht ganz so schlimm, denn die Arrays von JavaScript sind sehr einfach zu nutzen. Man muss ihnen keine Dimension zuweisen, und es kommt niemals zu Grenzverletzungen. Doch ihre Performance kann deutlich schlechter sein als bei echten Arrays. Der typeof-Operator unterscheidet nicht zwischen Arrays und Objekten. Um zu bestimmen, ob ein Wert ein Array ist, müssen Sie auch dessen constructor-Eigenschaft untersuchen: if (my_value && typeof my_value === 'object' && my_value.constructor === Array) { // my_value ist ein Array! }
Dieser Test liefert eine falsche negative Antwort zurück, wenn ein Array in einem anderen Frame oder Fenster erzeugt wurde. Dieser Test ist zuverlässiger, wenn der Wert in einem anderen Frame erzeugt werden konnte:
Max. Linie
if (my_value && typeof my_value === 'object' && typeof my_value.length === 'number' && !(my_value.propertyIsEnumerable('length')) { // my_value ist wirklich ein Array! }
Falsche Arrays This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
113
Links Das arguments-Array ist kein Array, sondern ein Objekt mit einem length-Member. Der Test identifiziert das arguments-Array als Array (was man sich manchmal wünscht), obwohl arguments keinerlei Array-Methoden besitzt. So oder so kann der Test fehlschlagen, wenn die propertyIsEnumerable-Methode überschrieben wird.
Falsch-Werte JavaScript besitzt eine überraschend große Menge von Falsch-Werten, wie Tabelle A-1 zeigt. Tabelle A-1: Die vielen Falsch-Werte von JavaScript Wert
Typ
0
Zahl
NaN (not a number)
Zahl
'' (Leer-String)
String
false
Boolesch
null
Objekt
undefined
Undefined
Diese Werte sind alle »falsch«, sind aber nicht unbedingt austauschbar. Das Folgende zum Beispiel die falsche Vorgehensweise, wenn man ermitteln will, ob bei einem Objekt ein Member fehlt: value = myObject[name]; if (value == null) { alert(name + ' not found.'); }
undefined ist der Wert fehlender Member, aber der Code testet auf null. Er verwendet den ==-Operator (siehe Anhang B), der »Typanpassung«, anstelle des zuverlässigeren Operators ===. Manchmal heben sich diese beiden Fehler auf, manchmal
nicht. undefined und NaN sind keine Konstanten. Sie sind globale Variablen und können
ihren Wert ändern. Das sollte nicht möglich sein, ist es aber. Machen Sie das nicht.
hasOwnProperty
Max. Linie
In Kapitel 3 wurde die Methode hasOwnProperty als Filter angeboten, um ein Problem mit der for...in-Anweisung zu umgehen. Leider ist hasOwnProperty eine Methode und kein Operator, d.h., sie kann in jedem Objekt durch eine andere Funktion ersetzt werden oder sogar durch einen Wert, der keine Funktion ist:
114
|
Anhang A: Furchtbare Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts var name; another_stooge.hasOwnProperty = null; // Ärger for (name in another_stooge) { if (another_stooge.hasOwnProperty(name)) { // Peng document.writeln(name + ': ' + another_stooge[name]); } }
Object Die Objekte von JavaScript sind nie wirklich leer, weil sie Member aus der Prototypkette auswählen können. Manchmal ist das von Bedeutung. Nehmen wir beispielsweise an, dass Sie ein Programm schreiben, das die Anzahl der Vorkommen jedes Wortes in einem Text zählt. Wir können die toLowerCase-Methode verwenden, um den Text in Kleinbuchstaben umzuwandeln, und können dann die splitMethode nutzen, die mithilfe eines regulären Ausdrucks ein Array von Wörtern erzeugt. Wir können die Wörter dann durchgehen und festhalten, wie oft jedes Wort vorkommt: var i; var word; var text = "This oracle of comfort has so pleased me, " + "That when I am in heaven I shall desire " + "To see what this child does, " + "and praise my Constructor."; var words = text.toLowerCase( ).split(/[\s,.]+/); var count = {}; for (i = 0; i < words.length; i += 1) { word = words[i]; if (count[word]) { count[word] += 1; } else { count[word] = 1; } }
Wenn wir uns die Ergebnisse ansehen, enthält count['this'] den Wert 2, und count.heaven enthält den Wert 1, aber count.constructor enthält einen seltsam aussehenden String. Der Grund dafür besteht darin, dass das count-Objekt von Object. prototype erbt und Object.prototype ein Member namens constructor besitzt, dessen Wert Object lautet. Der Operator += führt, genau wie der Operator +, eine Verkettung anstelle einer Addition durch, wenn die Operanden keine Zahlen sind. Object ist eine Funktion, d.h., += wandelt sie irgendwie in einen String um und verkettet sie mit 1.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Object
|
115
Links Wir können solche Probleme auf die gleiche Weise vermeiden, die wir auch bei for...in genutzt haben: Wir überprüfen die Mitgliedschaft mit der hasOwnPropertyMethode, oder wir suchen nach bestimmten Typen. In diesem Fall war unser Test auf den »Wahrheitsgrad« von count[word] nicht genau genug. Wir hätten stattdessen Folgendes schreiben sollen: if (typeof count[word] === 'number') {
Max. Linie
Max. Linie 116
|
Anhang A: Furchtbare Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Anhang B
ANHANG B
Schlechte Seiten
And, I pray thee now, tell me for which of my bad parts didst thou first fall in love with me? – William Shakespeare, Much Ado About Nothing
In diesem Anhang möchte ich einige der problematischen Features von JavaSript vorstellen, die sich allerdings einfach vermeiden lassen. Indem Sie diese Features meiden, machen Sie JavaScript zu einer besseren Sprache und sich selbst zu einem besseren Programmierer.
== JavaScript besitzt zwei Gruppen von Gleichheitsoperatoren: === und !== sowie deren böse Zwillinge == und !=. Die Guten funktionieren so, wie man das erwartet. Sind die beiden Operanden vom gleichen Typ und haben sie den gleichen Wert, dann erzeugt === true und !== false. Die bösen Zwillinge machen das Richtige, wenn die Operanden vom gleichen Typ sind, doch wenn unterschiedliche Typen im Spiel sind, versuchen sie, die Werte zu erzwingen. Die Regeln, nach denen das geschieht, sind kompliziert und nur schwer zu merken. Hier einige der interessanten Fälle: '' == '0' 0 == '' 0 == '0'
// false // true // true
false == 'false' false == '0'
// false // true
false == undefined // false false == null // false null == undefined // true
Max. Linie
' \t\r\n ' == 0
Max. Linie
// true
| This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
117
Links Dieses Fehlen von Transitivität ist alarmierend. Mein Rat lautet, die bösen Zwillinge niemals zu verwenden. Nutzen Sie stattdessen immer === und !==. Alle obigen Vergleiche erzeugen false beim ===-Operator.
with-Anweisung JavaScript besitzt eine with-Anweisung, die als Kurzform für den Zugriff auf die Eigenschaften eines Objekts gedacht war. Leider sind die Ergebnisse manchmal nicht vorhersehbar, weshalb man sie meiden sollte. Die Anweisung with (obj) { a = b; }
macht das Gleiche wie: if (obj.a === undefined) { a = obj.b === undefined ? b : obj.b; } else { obj.a = obj.b === undefined ? b : obj.b; }
Sie entspricht daher einer der folgenden Anweisungen: a = b; a = obj.b; obj.a = b; obj.a = obj.b;
Wenn Sie das Programm lesen, können Sie nicht vorhersagen, welche dieser Anweisungen ausgeführt wird. Das kann sich von einer Programmausführung zur nächsten ändern. Das kann auch variieren, während das Programm läuft. Wenn Sie ein Programm nicht lesen können, ohne zu verstehen, was vorgeht, können Sie sich auch nicht sicher sein, dass es genau das tut, was Sie wollen. Allein durch die Tatsache, dass sie in der Sprache vorhanden ist, verlangsamt die with-Anweisung JavaScript-Prozessoren deutlich, weil sie die lexikalische Bindung der Variablennamen frustriert. Sie war gut gedacht, aber die Sprache wäre ohne sie besser dran.
eval
Max. Linie
Die eval-Funktion übergibt einen String an den JavaScript-Compiler und führt das Ergebnis aus. Sie ist das meistmissbrauchte Feature von JavaScript. Sie wird meistens von Leuten genutzt, die ein nur unvollständiges Verständnis der Sprache haben. Wenn Sie zum Beispiel die Punktnotation kennen, aber die Indexnotation ignorieren, könnten Sie
118
|
Anhang B: Schlechte Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts eval("myValue = myObject." + myKey + ";");
statt myvalue = myObject[myKey];
schreiben. Die eval-Form ist wesentlich schwerer zu lesen. Diese Form ist deutlich langsamer, weil der Compiler ausgeführt werden muss, nur um eine triviale Zuweisung vorzunehmen. Das ist auch ein Hindernis für JSLint (siehe Anhang C), da die Fähigkeit des Tools, Probleme zu erkennen, deutlich reduziert wird. Die eval-Funktion untergräbt auch die Sicherheit Ihrer Anwendung, weil sie dem evaluierten Text zu viele Rechte gewährt. Und sie wirkt sich in der gleichen Weise negativ auf die Performance der Sprache aus wie die with-Anweisung. Der Function-Konstruktor ist eine andere Form des eval und sollte ebenfalls vermieden werden. Der Browser stellt setTimeout- und setInterval-Funktionen bereit, die String- oder Funktionsargumente verarbeiten können. Bei String-Argumenten fungieren setTimeout und setInterval als eval. Die String-Argumentform sollte daher ebenfalls vermieden werden.
continue-Anweisung Die continue-Anweisung springt an den Anfang einer Schleife. Ich habe noch nie Code gesehen, der von einen Umbau ohne continue-Anweisung nicht profitiert hätte.
switch-Fallthrough Die switch-Anweisung wurde nach der berechneten go to-Anweisung von FORTRAN IV modelliert. Jeder Fall rutscht in den nächsten Fall durch (»Fallthrough«), solange Sie das nicht explizit unterbinden. Jemand schrieb mir einmal, dass JSLint eine Warnung ausgeben sollte, wenn ein Fall in einen anderen Fall durchrutscht. Er argumentierte, dass das eine sehr häufige Fehlerquelle sei und dass dieser Fehler im Code nur schwer zu erkennen ist. Ich antwortete, dass das zwar alles wahr sei, aber dass der Vorteil, den man durch die Kompaktheit des Fallthroughs habe, die Chancen eines Fehlers kompensiere.
Max. Linie
Am nächsten Tag meldete er einen Fehler in JSLint. Es identifizierte einen Fehler falsch. Ich untersuchte das Ganze, und es stellte sich heraus, dass es an einer ungewollt durchrutschenden case-Anweisung lag. In diesem Moment wurde ich erleuchtet. Ich verwende keine Fallthroughs mehr. Diese Disziplin macht es wesentlich einfacher, ungewollte Fallthroughs zu erkennen.
switch-Fallthrough This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
119
Max. Linie
Links Die schlimmsten Features einer Sprache sind nicht die Features, die ganz offensichtlich gefährlich oder nutzlos sind. Diese lassen sich einfach vermeiden. Die schlimmsten Features sind die attraktiven Plagen, also die Features, die sowohl nützlich als auch gefährlich sind.
Blockfreie Anweisungen Eine if-, while-, do- oder for-Anweisung kann einen Block oder eine einzelne Anweisung enthalten. Die Form mit nur einer Anweisung ist eine weitere attraktive Plage. Sie hat den Vorteil, dass man zwei Zeichen einspart, was ein recht fragwürdiger Vorteil ist. Sie verschleiert die Struktur des Programms in einer Weise, die nachfolgende Entwickler leicht Fehler einfügen lässt. Zum Beispiel kann if (ok) t = true;
zu if (ok) t = true; advance( );
werden, was wie if (ok) { t = true; advance( ); }
aussieht, aber tatsächlich Folgendes bedeutet: if (ok) { t = true; } advance( );
Programme, die scheinbar das eine, in Wirklichkeit aber das andere tun, sind wesentlich schwerer unter Kontrolle zu halten. Die disziplinierte und konsistente Nutzung von Blöcken macht es einfacher, die Kontrolle zu behalten.
++ -Die Inkrement- und Dekrementoperatoren ermöglichen einen extrem kompakten Stil. Bei Sprachen wie C ermöglichen sie die Entwicklung von Einzeilern zum Kopieren von Strings: for (p = src, q = dest; !*p; p++, q++) *q = *p;
Max. Linie
Es hat sich aber gezeigt, dass sie auch einen etwas sorglosen Programmierstil fördern. Ein Großteil der Pufferüberlauf-Bugs, die so furchtbare Sicherheitslücken aufgerissen haben, hat seine Ursache in Code wie diesem.
120
|
Anhang B: Schlechte Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts In der Praxis habe ich beobachtet, dass mein Code bei der Verwendung von ++ und -- zu kompakt, zu trickreich und zu kryptisch wurde. Daher verwende ich sie aus disziplinarischen Gründen nicht mehr. Das Ergebnis ist meiner Meinung nach ein klarerer Programmierstil.
Bitorientierte Operatoren JavaScript besitzt die gleichen bitorientierten Operatoren wie Java: & | ^ ~ >> >>> <<
and or xor not Rechtsshift mit Vorzeichen Rechtsshift ohne Vorzeichen Linksshift
Bei Java arbeiten die bitorientierten Operatoren mit Integerwerten. JavaScript besitzt keine Integerwerte. Es kennt nur doppelt genaue Fließkommazahlen. Daher wandeln die bitorientierten Operatoren ihre Operanden in Integerwerte um, erledigen ihre Arbeit und wandeln sie dann wieder um. Bei den meisten Sprachen sind diese Operatoren sehr hardwarenah und sehr schnell. Bei JavaScript sind sie sehr weit weg von der Hardware und sehr langsam. JavaScript wird sehr selten zur Bitmanipulation verwendet. Dementsprechend ist bei JavaScript-Programmen ein & wahrscheinlich ein falsch geschriebener &&-Operator. Das Vorhandensein der bitorientierten Operatoren reduziert die Redundanz der Sprache ein wenig und macht es Fehlern einfacher, sich zu verstecken.
function-Anweisung vs. function-Ausdruck JavaScript besitzt eine function-Anweisung und einen function-Ausdruck. Das ist sehr verwirrend, weil sie genau gleich aussehen. Eine function-Anweisung ist eine Kurzform für eine var-Anweisung mit einem Funktionswert. Die Anweisung function foo( ) {}
bedeutet in etwa das Gleiche wie: var foo = function foo( ) {};
Max. Linie
Im gesamten Buch verwende ich die zweite Form, weil sie ganz deutlich macht, dass foo eine Variable ist, die einen Funktionswert enthält. Um die Sprache gut nutzen zu können, ist es wichtig zu verstehen, dass Funktionen Werte sind.
function-Anweisung vs. function-Ausdruck This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
121
Max. Linie
Links function-Anweisungen dienen dem Hochziehen. Das bedeutet, dass eine function,
ganz egal wo sie platziert wird, immer an den Anfang des Geltungsbereichs, in dem sie definiert ist, »hochgezogen« wird. Dadurch wird die Aufforderung gelockert, dass Funktionen vor ihrer Verwendung deklariert werden sollten, was meiner Meinung nach zu Nachlässigkeiten führt. Es verhindert auch die Verwendung von function-Anweisungen in if-Anweisungen. Es zeigt sich, dass die meisten Browser function-Anweisungen in if-Anweisungen erlauben, allerdings ist deren Interpretation nicht immer gleich. Das führt zu Portabilitätsproblemen. Das erste Element in einer Anweisung kann kein function-Ausdruck sein, weil die offizielle Grammatik davon ausgeht, dass eine Anweisung, die mit dem Wort function beginnt, eine function-Anweisung ist. Um das zu umgehen, klammern Sie den function-Ausdruck: (function ( ) { var hidden_variable; // Diese Funktion kann sich auf die Umgebung // auswirken, führt aber keine neuen globalen // Variablen ein. })( );
Typisierte Wrapper JavaScript besitzt eine Reihe typisierter Wrapper. Zum Beispiel erzeugt new Boolean(false)
ein Objekt, das eine valueOf-Methode besitzt, die den gewrappten Wert zurückgibt. Das hat sich als völlig unnötig und gelegentlich sogar als verwirrend erwiesen. Arbeiten Sie nicht mit new Boolean oder new Number oder new String. Meiden Sie auch new Objekt und new Array. Verwenden Sie stattdessen {} und [].
new Der new-Operator von JavaScript erzeugt ein neues Objekt, das vom prototypeMember des Operanden erbt und dann den Operanden aufruft, um das neue Objekt an this zu binden. Das gibt dem Operanden (der besser eine Konstruktorfunktion wäre) die Gelegenheit, das neue Objekt anzupassen, bevor es zurückgegeben wird.
Max. Linie
Wenn Sie vergessen, den new-Operator zu nutzen, haben Sie es stattdessen mit einem einfachen Funktionsaufruf zu tun, und this wird an das globale Objekt gebunden und nicht an das neue Objekt. Das bedeutet, dass Ihre Funktion bei der Initialisierung neuer Member an den globalen Variablen herumfuhrwerkt. Das ist eine
122
|
Anhang B: Schlechte Seiten This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts ganz üble Sache. Es gibt keine Warnung vom Compiler, und es gibt keine Warnung zur Laufzeit. Per Konvention sollen Funktionen, die mit new verwendet werden sollen, einen großgeschriebenen Anfangsbuchstaben erhalten, und Namen mit einem großgeschriebenen Anfangsbuchstaben sollen nur für Konstruktorfunktionen verwendet werden, die das new-Präfix verwenden. Diese Konvention gibt Ihnen einen sichtbaren Hinweis auf teure Fehler, die die Sprache selbst einfach übersieht. Eine noch bessere Strategie besteht darin, new überhaupt nicht zu verwenden.
void In vielen Sprachen ist void ein Typ, der keine Werte zurückgibt. Bei JavaScript ist void ein Operator, der einen Operanden verlangt und undefined zurückgibt. Das ist nicht nützlich und sehr verwirrend. Vermeiden Sie void.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
void
|
123
FirstLeft. Anhang C C ANHANG
JSLint
What error drives our eyes and ears amiss? – William Shakespeare, The Comedy of Errors
Als C noch eine junge Programmiersprache war, gab es verschiedene weitverbreitete Programmierfehler, die von den noch primitiven Compilern nicht abgefangen wurden. Daher wurde ein Zusatzprogramm namens lint entwickelt, das eine Quelldatei auf Probleme untersucht. Während C erwachsen wurde, wurde die Definition der Sprache gestrafft, um einige Unsicherheiten zu eliminieren, und Compiler lieferten bessere Warnungen, so dass lint nicht länger benötigt wurde. JavaScript ist in dieser Hinsicht eine jung gebliebene Sprache. Sie wurde ursprünglich dafür ausgelegt, kleine Dinge auf Webseiten zu erledigen, Dinge, für die Java zu groß und schwerfällig war. Aber JavaScript ist eine durchaus leistungsfähige Sprache und wird nun auch in größeren Projekten eingesetzt. Viele Features, die dafür gedacht waren, die Nutzung der Sprache zu vereinfachen, führen bei größeren Projekten zu Schwierigkeiten. Ein lint für JavaScript ist notwendig: JSLint, ein Programm, das die JavaScript-Syntax überprüft und verifiziert. JSLint ist ein Qualitätswerkzeug für JavaScript. Es nimmt sich einen Quelltext und untersucht ihn. Findet es ein Problem, gibt es eine Nachricht zurück, die das Problem und dessen ungefähre Lage im Quelltext erläutert. Dieses Problem ist nicht notwendigerweise ein Syntaxfehler, auch wenn das oft der Fall ist. JSLint achtet auf einige Stil-Konventionen sowie auf strukturelle Probleme. Es stellt nicht sicher, dass Ihr Programm richtig ist. Es bietet nur ein weiteres Paar Augen an, die bei der Erkennung von Problemen helfen.
Max. Linie
JSLint definiert eine professionelle Untermenge von JavaScript, eine striktere Sprache, als sie von der dritten Auflage der ECMAScript Language Specification definiert wird. Diese Untermenge orientiert sich eng an den Stil-Empfehlungen aus Kapitel 9.
124
|
Anhang C: JSLint
Max. Linie
Rechts JavaScript ist eine schluderige Sprache, tief im Inneren aber eine elegante, bessere Sprache. JSLint hilft Ihnen dabei, in dieser besseren Sprache zu programmieren und einen Großteil der Schluderigkeit zu vermeiden. JSLint finden Sie unter http://www.JSLint.com/.
Nicht definierte Variablen und Funktionen Das größte Problem von JavaScript ist seine Abhängigkeit von (zum Teil implizierten) globalen Variablen. Wird eine Variable nicht explizit deklariert (üblicherweise über die var-Anweisung), dann geht JavaScript davon aus, dass diese Variable global ist. Das kann falsch geschriebene Namen und andere Probleme verschleiern. JSLint erwartet, dass man alle Variablen und Funktionen deklariert, bevor sie genutzt oder aufgerufen werden. Das ermöglicht die Erkennung implizierter globaler Variablen. Gleichzeitig ist das eine gute Vorgehensweise, da die Programme einfacher zu lesen sind. Manchmal ist eine Datei von globalen Variablen und Funktionen abhängig, die an anderen Stellen definiert sind. Sie können diese JSLint bekannt geben, indem Sie einen Kommentar in Ihre Datei aufnehmen, der alle globalen Funktionen und Objekte auflistet, von denen Ihr Programm abhängig ist, die aber nicht in diesem Programm bzw. in dieser Skriptdatei enthalten sind. Ein Kommentar mit globalen Deklarationen kann verwendet werden, um alle Namen aufzulisten, die Sie bewusst als globale Variablen verwenden. JSLint kann diese Information nutzen, um Schreibfehler und vergessene var-Deklarationen zu erkennen. Eine solche globale Deklaration sieht wie folgt aus: /*global getElementByAttribute, breakCycles, hanoi */
Eine globale Deklaration beginnt mit /*global. Beachten Sie, dass kein Leerzeichen vor dem g steht. Sie können so viele /*global-Kommentare nutzen, wie Sie wollen. Diese müssen erscheinen, bevor Sie die in ihnen angegebenen Variablen nutzen. Einige globale Variablen können für Sie vordefiniert werden (siehe den noch folgenden Abschnitt »Optionen«). Wählen Sie die Option »Assume a browser« (browser), um die globalen Standard-Eigenschaften vorzudefinieren, die von Webbrowsern bereitgestellt werden, wie etwa window, document und alert. Wählen Sie die Option »Assume Rhino« (rhino), um die globalen Poperties der Rhino-Umgebung vorzudefinieren. Wählen Sie die Option »Assume a Yahoo Widget« (widget), um die globalen Eigenschaften der Yahoo!-Widgets-Umgebung vorzudefinieren.
Max. Linie
Max. Linie Nicht definierte Variablen und Funktionen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
125
Links Member Da JavaScript eine schwach typisierte Sprache mit dynamischen Objekten ist, kann man während der Kompilierung nicht ermitteln, ob Eigenschaftsnamen korrekt geschrieben wurden. JSLint bietet einem hier einiges an Hilfe an. Am Ende des Berichts gibt JSLint einen /*members*/-Kommentar aus. Dieser enthält alle Namen und String-Literale, die bei der Punktnotation, Indexnotation und bei Objektliteralen verwendet wurden, um Member des Objekts anzusprechen. Sie können diese Liste auf Schreibfehler untersuchen. Nur einmal verwendete Membernamen werden kursiv dargestellt, um sie einfacher erkennen zu können. Sie können den /*members*/-Kommentar in Ihre Skriptdatei einfügen. JSLint vergleicht dann die Schreibweise aller Eigenschaftsnamen mit dieser Liste. Auf diese Weise kann JSLint Schreibfehler für Sie erkennen: /*members doTell, iDoDeclare, mercySakes, myGoodness, ohGoOn, wellShutMyMouth */
Optionen Die JSLint-Implementierung akzeptiert ein Optionen-Objekt, das es Ihnen ermöglicht, die Untermenge von JavaScript zu bestimmen, die Ihnen akzeptabel erscheint. Sie können diese Optionen auch im Skript selbst angeben. Eine solche Optionen-Spezifikation sieht etwa so aus: /*jslint nomen: true, evil: false */
Eine Optionen-Spezifikation beginnt mit /*jslint. Beachten Sie, dass kein Leerzeichen vor dem j steht. Die Spezifikation enthält eine Folge von Schlüssel/Wert-Paaren, bei denen die Namen JSLint-Optionen sind und die Werte mit true oder false angegeben werden. Eine Optionen-Spezifikation hat Vorrang vor dem OptionenObjekt. Alle Optionen sind standardmäßig auf false gesetzt. Tabelle C-1 führt die für JSLint verfügbaren Optionen auf. Tabelle C-1: JSLint-Optionen
Max. Linie
Option
Bedeutung
adsafe
true, wenn ADsafe.org-Regeln erzwungen werden sollen
bitwise
true, wenn bitorientierte Operatoren nicht erlaubt sind
browser
true, wenn die globalen Standardvariablen des Browsers vordefiniert werden sollen
cap
true, wenn HTML in Großbuchstaben erlaubt ist
debug
true, wenn debugger-Anweisungen erlaubt sind
eqeqeq
true, wenn === verlangt wird
126
|
Anhang C: JSLint
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabelle C-1: JSLint-Optionen (Fortsetzung) Option
Bedeutung
evil
true, wenn eval nicht erlaubt ist
forin
true, wenn ungefilterte for...in-Anweisungen erlaubt sind
fragment
true, wenn HTML-Fragmente erlaubt sind
glovar
true, wenn var zur Deklaration globaler Variablen nicht erlaubt ist
laxbreak
true, wenn Breaks in Anweisung nicht geprüft werden sollen
nomen
true, wenn Namen geprüft werden sollen
on
true, wenn HTML-Eventhandler nicht erlaubt sind
passfail
true, wenn die Untersuchung beim ersten Fehler anhalten soll
plusplus
true, wenn ++ und -- nicht erlaubt sind
rhino
true, wenn die globalen Variablen der Rhino-Umgebung vordefiniert werden sollen
undef
true, wenn nicht definierte globale Variablen ein Fehler sind
white
true, wenn strenge Whitespace-Regeln gelten
widget
true, wenn die globalen Variablen der Yahoo! Widgets vordefiniert werden sollen
Semikolon JavaScript verwendet eine C-artige Syntax, was die Verwendung eines Semikolons zum Abschluss von Anweisungen erfordert. JavaScript versucht über einen automatischen Einfügemechanismus, diese Semikola optional zu halten. Das ist gefährlich.
Max. Linie
Wie C kennt auch JavaScript die Operatoren ++ und -- und (, die als Präfixe oder Suffixe fungieren können. Mithilfe des Semikolons lassen sich Mehrdeutigkeiten vermeiden.
Semikolon This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
127
Max. Linie
Links Bei JavaScript kann ein Linefeed als Whitespace interpretiert werden oder als Semikolon fungieren. Das ersetzt eine Mehrdeutigkeit durch eine andere. JSLint erwartet, dass jeder Anweisung ein ; folgt. Die Ausnahmen von dieser Regel sind for, function, if, switch, try und while. JSLint achtet auf unnötige Semikola und Leeranweisungen.
Zeilenumbruch Als weiteren Schutz vor der Verschleierung von Fehlern durch den Semikolon-Einfügemechanismus verlangt JSLint, dass lange Anweisungen nur nach den folgenden Interpunktionszeichen oder Operatoren umbrochen werden: , . ; : { } ( [ = < > ? ! + - * / % ~ ^ | & == != <= >= += -= *= /= %= ^= |= &= << >> || && === !== <<= >>= >>> >>>=
JSLint möchte nicht, dass lange Anweisungen nach einem Identifier, einem String, einer Zahl, einem schließenden Element oder einem Suffix-Operator umbrochen werden: ) ] ++ --
JSLint toleriert »laxe« Zeilenumbrüche über die Option »Tolerate sloppy line breaking« (laxbreak). Das automatische Einfügen von Semikola kann Kopieren/Einfügen-Fehler verschleiern. Wenn Sie Zeilen immer nach Operatoren umbrechen, hat es JSLint leichter, diese Fehler zu finden.
Komma Der Komma-Operator kann zu extrem trickreichen Ausdrücken führen. Er kann aber auch einige Programmierfehler verschleiern. JSLint will das Komma als Separator verwendet wissen, nicht als Operator (außer bei den Initialisierungs- und Inkrementierungsteilen der for-Anweisung). Es erwartet auch keine elidierten Elemente in Array-Literalen. Zusätzliche Kommata sollten nicht verwendet werden. Ein Komma darf nicht hinter dem letzten Element eines Array- oder Objektliterals stehen, da es von einigen Browsern fehlinterpretiert wird.
Blöcke JSLint erwartet, dass if- und for-Anweisungen mit Blöcken arbeiten – d.h. mit Anweisungen, die in geschweiften Klammern ( {}) stehen.
Max. Linie
JavaScript erlaubt es, ein if wie folgt zu schreiben:
128
|
Anhang C: JSLint
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts if (bedingung) anweisung;
Man weiß, dass diese Form bei Projekten, bei denen mehrere Programmierer am gleichen Code arbeiten, zu Fehlern führt. Daher erwartet JSLint die Verwendung eines Blocks: if (bedingung) { anweisungen; }
Die Erfahrung zeigt, dass diese Form robuster ist.
Verbotene Blöcke Bei vielen Sprachen leitet ein Block einen neuen Geltungsbereich ein. In einem Block eingeführte Variablen sind außerhalb dieses Blocks nicht sichtbar. Bei JavaScript leiten Blöcke keinen neuen Geltungsbereich ein. Es gibt nur einen Funktionsgeltungsbereich. Eine in einer Funktion eingeführte Variable ist überall innerhalb der Funktion sichtbar. JavaScript-Blöcke verwirren erfahrene Programmierer und führen zu Fehlern, weil die vertraute Syntax ein falsches Versprechen macht. JSLint erwartet Blöcke bei function-, if-, switch-, while-, for-, do- und try-Anweisungen und nirgendwo sonst. Ausnahmen sind blockfreie if-Anweisungen, else und for...in.
Ausdrucksanweisungen Als Ausdrucksanweisung gelten eine Zuweisung, ein Funktions- bzw. Methodenaufruf und delete. Alle anderen Ausdrucksanweisungen werden als Fehler betrachtet.
for...in-Anweisung Die for...in-Anweisung erlaubt es, die Namen aller Eigenschaften eines Objekts durchzugehen. Leider geht sie aber auch alle Member durch, die über die Prototypenkette geerbt wurden. Das hat den unangenehmen Nebeneffekt, dass auch Methoden zurück geliefert werden, während man eigentlich an Daten-Membern interessiert ist.
Max. Linie
Der Body jeder for...in-Anweisung sollte daher in einer if-Anweisung stehen, die als Filter fungiert. if kann einen bestimmten Typ oder Wertebereich wählen und Funktionen oder Eigenschaften aus dem Prototyp ausschließen. Hier ein Beispiel:
for...in-Anweisung This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
129
Max. Linie
Links for (name in object) { if (object.hasOwnProperty(name)) { .... } }
switch-Anweisung Ein typischer Fehler bei switch-Anweisungen besteht darin, nicht jeden Fall mit einer break-Anweisung abzuschließen, was zu einem unerwünschten »Durchrutschen« führen kann. JSLint erwartet vor dem nächsten Fall bzw. vor default eine der folgenden Anweisungen: break, return oder throw.
var-Anweisung JavaScript erlaubt var-Definitionen überall innerhalb einer Funktion. JSLint ist da strenger. JSLint erwartet, dass • eine var nur einmal deklariert wird und dass die Deklaration vor der Verwendung erfolgt. • eine Funktion deklariert wird, bevor sie verwendet wird. • Parameter nicht auch als vars deklariert werden. JSLint erwartet nicht, dass • das arguments-Array als var deklariert wird. • eine Variable in einem Block deklariert wird. Das liegt daran, dass JavaScriptBlöcke keinen Block-Geltungsbereich besitzen. Das kann unerwartete Konsequenzen haben, weshalb alle Variablen am Anfang des Funktionsbodys definiert werden sollten.
with-Anweisung Die with-Anweisung war als Kürzel gedacht, um besser auf Member in tief verschachtelten Objekten zugreifen zu können. Leider verhält sie sich sehr schlecht, wenn neue Member gesetzt werden. Verwenden Sie die with-Anweisung niemals. Verwenden Sie stattdessen var. JSLint will keine with-Anweisung sehen.
Max. Linie
Max. Linie 130
|
Anhang C: JSLint
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts = JSLint möchte keine Zuweisung im Bedingungsteil einer if- oder while-Anweisung sehen. Das liegt daran, dass mit if (a = b) { ... }
wohl eher if (a == b) { ... }
gemeint ist. Wollen Sie tatsächlich etwas zuweisen, stellen Sie es in ein zusätzliches Paar Klammern: if ((a = b)) { ... }
== und != Die Operatoren == und != führen vor dem Vergleich eine Typanpassung durch. Das ist schlecht, weil ' \f\r \n\t ' == 0 zu true evaluiert. Dadurch können Typfehler verschleiert werden. Wenn Sie mit einem der folgenden Typen vergleichen, sollten Sie immer die Operatoren === bzw. !== verwenden, weil hier keine Typanpassung erfolgt: 0 '' undefined null false true
Soll eine Typanpassung erfolgen, sollten Sie die Kurzform verwenden. Statt (foo != 0)
verwenden Sie einfach: (foo)
Und statt (foo == 0)
verwenden Sie einfach: (!foo)
Die Verwendung der Operatoren === und !== ist immer zu bevorzugen. Es gibt die Option »Disallow == and !=« (eqeqeq), die in allen Fällen auf der Verwendung von === und !== besteht.
Max. Linie
Max. Linie == und != This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
131
Links Label JavaScript erlaubt es jeder Anweisung, ein Label zu besitzen, und Label besitzen einen separaten Namensraum. JSLint ist da strenger. JSLint erwartet Label nur bei Anweisungen, die mit break: switch, while, do und for interagieren. JSLint erwartet, dass sich Label von Variablen und Parametern unterscheiden.
Unerreichbarer Code JSLint erwartet, dass auf ein return, break, continue oder throw ein } oder case oder default folgt.
Verwirrendes Plus und Minus JSLint erwartet, dass auf ein + kein + oder ++ folgt und auf - kein - oder --. Ein falsch platziertes Leerzeichen macht aus + + ein ++, ein Fehler, der nur schwer zu erkennen ist. Verwenden Sie Klammern, um Verwirrung zu vermeiden.
++ und -Die Operatoren ++ (Inkrement) und -- (Dekrement) sind dafür bekannt, schlechten Code hervorzubringen, weil sie trickreiche Ausdrücke fördern. Sie tragen zu fehlerhaften Architekturen bei, indem sie Viren und andere Sicherheitslücken begünstigen. Die JSLint-Option plusplus verbietet die Verwendung dieser Operatoren.
Bitorientierte Operatoren JavaScript besitzt keinen Integertyp, kennt aber bitorientierte Operatoren. Die bitorientierten Operatoren wandeln ihre Operanden von Fließkomma- in Integerwerte um und wieder zurück, d.h., sie sind nicht annähernd so effektiv wie in C oder anderen Sprachen. Sie sind in Browseranwendungen nur selten nützlich. Ihre Ähnlichkeit mit logischen Operatoren kann einige Programmierfehler verschleiern. Die Option bitwise verbietet die Verwendung dieser Operatoren.
eval ist böse Max. Linie
Die eval-Funktion und ihre Verwandten (Function, setTimeout und setInterval) bieten Zugriff auf den JavaScript-Compiler. Das ist manchmal nützlich, in den meisten Fällen aber ein Hinweis auf extrem schlechten Code. Die eval-Funktion ist das am häufigsten missbrauchte Feature von JavaScript.
132
|
Anhang C: JSLint
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts void In den meisten C-artigen Sprachen ist void ein Typ. Bei JavaScript ist void ein Präfixoperator, der immer undefined zurückgibt. JSLint möchte void nicht sehen, weil es verwirrend und nicht besonders nützlich ist.
Reguläre Ausdrücke Reguläre Ausdrücke werden in einer sehr knappen und kryptischen Notation geschrieben. JSLint sucht nach Dingen, die zu Portabilitätsproblemen führen könnten. Es versucht auch, visuelle Mehrdeutigkeiten zu beheben, indem es Vorschläge zum expliziten Escaping macht. JavaScripts Syntax für Regex-Literale überlädt das /-Zeichen. Um Mehrdeutigkeiten zu vermeiden, erwartet JSLint vor einem reglären Ausdruck eines der Zeichen ( oder = oder : oder ,.
Konstruktoren und new Konstruktoren sind Funktionen, die mit dem Präfix new verwendet werden sollen. Das new-Präfix erzeugt ein neues Objekt, das auf dem Prototyp der Funktion basiert, und bindet das Objekt an den implizierten this-Parameter der Funktion. Wenn Sie das new-Präfix vergessen, wird kein neues Objekt angelegt, und this wird an das globale Objekt gebunden. Das ist ein schwerwiegender Fehler. JSLint unterstützt die Konvention, dass die Konstruktorfunktion einen Namen mit einem großgeschriebenen Anfangsbuchstaben hat. JSLint erwartet keinen Aufruf einer Funktion, deren Anfangsbuchstabe großgeschrieben wird, ohne dass vor ihr ein new steht. JSLint erwartet kein new-Präfix vor einer Funktion, deren Namen nicht mit einem Großbuchstaben beginnt. JSLint verbietet die Wrapper-Formen new Number, new String oder new Boolean. JSLint verbietet new Objekt (verwenden Sie stattdessen {}). JSLint verbietet new Array (verwenden Sie stattdessen []).
Was nicht untersucht wird JSLint führt keine Flussanalyse durch, um zu ermitteln, ob Variablen ein Wert zugewiesen wird, bevor sie genutzt werden. Das liegt daran, dass Variablen einen Wert (undefined) erhalten, der für viele Anwendungen ein vernünftiger Standardwert ist.
Max. Linie
JSLint führt keine globale Analyse durch. Es versucht nicht zu ermitteln, ob die mit new verwendeten Funktionen wirklich Konstruktoren sind (es achtet nur auf die Schreibweise) oder ob Methodennamen korrekt geschrieben wurden.
Was nicht untersucht wird This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
133
Max. Linie
Links HTML JSLint ist in der Lage, HTML-Text zu verarbeiten. Es kann JavaScript-Inhalte inspizieren, die zwischen <script>...-Tags und in Eventhandlern stehen. Es untersucht auch die HTML-Inhalte auf der Suche nach Elementen, die JavaScript bekanntermaßen in die Quere kommen: • Alle Tagnamen müssen kleingeschrieben sein. • Alle Tags, die ein schließendes Tag kennen (wie ), müssen ein schließendes Tag besitzen. • Alle Tags müssen korrekt geschachtelt sein. • Die Entität < muss für das Literal < verwendet werden. JSLint ist nicht ganz so pingelig, wie XHMTL fordert, aber strikter als die populären Browser. JSLint sucht auch nach Vorkommen von in String-Literalen. Sie sollten stattdessen immer <\/ schreiben. Der zusätzliche Backslash wird vom JavaScript-Compiler ignoriert, nicht aber vom HTML-Parser. Tricks wie dieser sollten nicht notwendig sein, sind es aber leider. Es gibt eine Option, die die Verwendung großgeschriebener Tag-Namen erlaubt. Es gibt auch eine Option, die die Verwendung von Inline-HTML-Eventhandlern erlaubt.
JSON JSLint kann auch überprüfen, ob JSON-Datenstrukturen wohlgeformt sind. Ist das erste von JSLint erkannte Zeichen ein { oder [, erzwingt es strikt die JSON-Regeln (siehe Anhang E).
Bericht Kann JSLint den Scan abschließen, generiert es einen Funktionsbericht. Dieser führt für jede Funktion Folgendes auf: • Die Nummer der Zeile an der sie beginnt • Ihren Namen; bei anonymen Funktionen »errät« JSLint den Namen • Die Parameter • Closure: die Variablen und Parameter, die in der Funktion deklariert sind und die in den inneren Funktionen verwendet werden
Max. Linie
• Variable: die in der Funktion deklarierten Variablen, die nur von der Funktion verwendet werden
134
|
Anhang C: JSLint
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts • Unused: die Variablen, die in der Funktion deklariert sind, aber nicht verwendet werden; das kann ein Zeichen für einen Fehler sein • Outer: von dieser Funktion verwendete Variablen, die in einer anderen Funktion deklariert sind • Global: globale Variablen, die von dieser Funktion verwendet werden • Label: Anweisungs-Label, die von dieser Funktion verwendet werden Der Bericht enthält auch eine Liste aller verwendeten Member-Namen.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Bericht
|
135
FirstLeft. Anhang D D ANHANG
Syntaxdiagramme
Thou map of woe, that thus dost talk in signs! – William Shakespeare, The Tragedy of Titus Andronicus
Max. Linie
Max. Linie 136
|
Anhang D: Syntaxdiagramme
Rechts
Max. Linie
Max. Linie Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
137
Links
Max. Linie
Max. Linie 138
|
Anhang D: Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts
Max. Linie
Max. Linie Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
139
Links
Max. Linie
Max. Linie 140
|
Anhang D: Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts
Max. Linie
Max. Linie Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
141
Links
Max. Linie
Max. Linie 142
|
Anhang D: Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts
Max. Linie
Max. Linie Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
143
Links
Max. Linie
Max. Linie 144
|
Anhang D: Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts
Max. Linie
Max. Linie Syntaxdiagramme This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
145
FirstLeft. Anhang E E ANHANG
JSON
Farewell: the leisure and the fearful time Cuts off the ceremonious vows of love And ample interchange of sweet discourse, Which so long sunder’d friends shoulddwell upon: God give us leisure for these rites of love! Once more, adieu: be valiant, and speed well! – William Shakespeare, The Tragedy of Richard the Third
Die JavaScript Object Notation (JSON) ist ein kompaktes Datenaustauschformat. Es basiert auf der JavaScript-Notation für Objektliterale, eine der besten Seiten von JavaScript. Zwar handelt es sich um eine Teilmenge von JavaScript, doch ist diese sprachunabhängig. Sie kann verwendet werden, um Daten zwischen Programmen auszutauschen, die in beliebigen modernen Programmiersprachen geschrieben wurden. Es handelt sich um ein Textformat, d.h., es kann von Menschen und Maschinen gelesen werden. Es ist einfach zu implementieren und zu nutzen. Sehr viele Informationen zu JSON finden Sie auf http://www.JSON.org/.
JSON-Syntax JSON besitzt sechs Arten von Werten: Objekte, Arrays, Strings, Zahlen, Boolesche Werte (true und false) sowie den Spezialwert null. Whitespace (Leerzeichen, Tabulatoren, Carriage-Return und Newline-Zeichen) können vor oder hinter jedem Wert stehen. Das erleichtert uns Menschen die Lesbarkeit von JSON-Texten. Whitespace kann weggelassen werden, um die Übertragungs- oder Speicherkosten zu reduzieren.
Max. Linie
Ein JSON-Objekt ist ein ungeordneter Container mit Schlüssel/Wert-Paaren. Ein Name kann ein beliebiger String sein. Ein Wert kann ein beliebiger JSON-Wert sein, auch ein Array oder Objekt. JSON-Objekte können beliebig tief verschachtelt werden, es ist im Allgemeinen aber effektiver, sie relativ flach zu halten. Die meisten Sprachen besitzen ein Feature, das JSON-Objekte recht einfach abbildet, etwa ein Objekt, Struct, Record, Dictionary, eine Hashtabelle, eine Eigenschaftsliste oder ein assoziatives Array. 146
|
Anhang E: JSON
Max. Linie
Rechts
Das JSON-Array ist eine geordnete Folge von Werten. Ein Wert kann dabei jeder JSON-Wert sein, auch ein Array und ein Objekt. Die meisten Sprachen besitzen ein Feature, das JSON-Arrays recht einfach abbildet, etwa Arrays, Vektoren, Listen oder Sequenzen.
Ein JSON-String steht in doppelten Anführungszeichen. Das Zeichen \ dient als Escape-Symbol. JSON erlaubt den Schutz des /-Zeichens, damit JSON in HTML<script>-Tags eingebettet werden kann. HTML verbietet die Sequenz außer zur Einleitung des -Tags. JSON erlaubt <\/, was zum gleichen Ergebnis führt, HTML aber nicht durcheinanderbringt.
Max. Linie
JSON-Zahlen entsprechen JavaScript-Zahlen. Eine führende Null ist bei Integerwerten nicht erlaubt, weil sie bei manchen Sprachen Oktalzahlen einleitet. Ein solches Durcheinander ist bei einem Datenaustauschformat natürlich nicht wünschenswert. Eine Zahl kann ein Integerwert, ein Fließkommawert oder eine Zahl in wissenschaftlicher Notation sein.
JSON-Syntax This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
147
Max. Linie
Links
4 hexadezimale Ziffern
Das war’s. Mehr gibt es bei JSON nicht. Die Entwurfsziele für JSON besagten, dass es minimal, portabel, textorientiert und eine Teilmenge von JavaScript sein sollte. Je weniger man vereinbaren muss, desto einfacher lassen sich Daten austauschen.
Max. Linie
Max. Linie 148
|
Anhang E: JSON
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts [ { "first": "Jerome", "middle": "Lester", "last": "Howard", "nick-name": "Curly", "born": 1903, "died": 1952, "quote": "nyuk-nyuk-nyuk!" }, { "first": "Harry", "middle": "Moses", "last": "Howard", "nick-name": "Moe", "born": 1897, "died": 1975, "quote": "Why, you!" }, { "first": "Louis", "last": "Feinberg", "nick-name": "Larry", "born": 1902, "died": 1975, "quote": "I'm sorry. Moe, it was an accident!" } ]
JSON sicher nutzen JSON ist in Webanwendungen besonders einfach zu nutzen, weil es sich bei JSON um JavaScript handelt. Ein JSON-Text kann über die eval-Funktion in eine nützliche Datenstruktur umgewandelt werden: var myData = eval('(' + myJSONText + ')');
(Die Klammern um den JSON-Text umgehen eine Mehrdeutigkeit der JavaScriptGrammatik.) Die eval-Funktion stellt uns aber vor riesige Sicherheitsprobleme. Ist es sicher, eval zum Parsing von JSON-Text zu verwenden? Die beste Technik, Daten von einem Server an den Browser zu übertragen, bietet momentan XMLHttpRequest. XMLHttpRequest kann Daten nur von dem Server abrufen, der auch den HTML-Code geliefert hat. Die Evaluierung eines Textes von diesem Server ist daher nicht weniger sicher als der eigentliche HTML-Code. Aber das setzt einen bösartigen Server voraus. Was tun, wenn der Server einfach inkompetent ist?
Max. Linie
Max. Linie JSON sicher nutzen This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
|
149
Links Ein inkompetenter Server könnte die JSON-Kodierung nicht korrekt vornehmen. Baut er JSON-Texte auf, indem er einfach ein paar Strings zusammenfügt, anstatt einen richtigen JSON-Encoder zu verwenden, dann könnte er ungewollt gefährliches Material übertragen. Wenn er als Proxy fungiert und JSON-Text einfach weiterleitet, ohne sich anzusehen, ob dieser auch wohlgeformt ist, könnte er wieder gefährliches Material senden. Diese Gefahr lässt sich vermeiden, wenn man die JSON.parse-Methode statt eval verwendet (siehe http://www.JSON.org/json2.js). JSON.parse löst eine Ausnahme aus, wenn der Text etwas Gefährliches enthält. Die Empfehlung lautet, immer JSON. parse statt eval zu verwenden, um sich vor inkompetenten Servern zu schützen. Es ist heutzutage auch gute Praxis, wenn der Browser einen sicheren Datenzugriff auf andere Server bietet. Es gibt eine weitere Gefahr bei der Interaktion von externen Daten und innerHTML. Ein gängiges Ajax-Muster ist, dass der Server ein HTML-Textfragment sendet, das der innerHTML-Eigenschaft eines HTML-Elements zugewiesen wird. Das ist eine sehr schlechte Praxis. Enthält der HTML-Text ein <script>-Tag (oder ein Äquivalent), dann kann ein bösartiges Skript ausgeführt werden. Auch hier könnte die Inkompetenz des Servers die Ursache sein. Worin genau besteht diese Gefahr? Wird ein bösartiges Skript ausgeführt, hat es Zugriff auf den gesamten Zustand und die Fähigkeiten der Seite. Es kann mit dem Server interagieren, und der Server ist nicht in der Lage, die bösartigen Requests von den gültigen Requests zu unterscheiden. Das bösartige Skript hat Zugriff auf das globale Objekt, was ihm wiederum den Zugriff auf alle Daten der Anwendung erlaubt (außer den Variablen, die sich in Closures verstecken). Es hat Zugriff auf das Document-Objekt, d.h., es kann auf alles zugreifen, was der Benutzer sieht. Das Adressfeld des Browsers und alle Anti-Phishing-Hinweise sagen dem Benutzer, dass der Dialog vertrauenswürdig ist. Das Document-Objekt ermöglicht bösartigen Skripten auch den Zugriff auf das Netzwerk, was das Nachladen weiterer bösartiger Skripten erlaubt oder das Probing von Sites innerhalb Ihrer Firewall oder das Senden der ausspionierten Geheimnisse an jeden Server auf der Welt. Diese Gefahr hat ihren Ursprung direkt im globalen Objekt von JavaScript, das mit Abstand die schlechteste der vielen schlechten Seiten von JavaScript darstellt. Diese Gefahren werden nicht durch Ajax oder JSON oder XMLHttpRequest oder das Web 2.0 (was auch immer das nun sein mag) verursacht. Diese Gefahren liegen im Browser, seit JavaScript eingeführt wurde, und sie werden dort bleiben, bis JavaScript abgelöst wird. Also Vorsicht.
Max. Linie
Max. Linie 150
|
Anhang E: JSON
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Rechts Ein JSON-Parser Im Folgenden sehen Sie die Implementierung eines JSON-Parsers in JavaScript: var json_parse = function () { // Die Funktion parst einen JSON-Text und erzeugt daraus eine JavaScript// Datenstruktur. Es handelt sich um einen einfachen Recursive-Descent-Parser. // Wir definieren die Funktion innerhalb einer anderen Funktion, // um das Anlegen globaler Variablen zu vermeiden. var at, // Index des aktuellen Zeichens ch, // Das aktuelle Zeichen escapee = { '"': '"' '\\': '\\', '/': '/', b: 'b', f: '\f', n: '\n', r: '\r' t: '\t' }, text, error = function (m) { // Rufe error auf, wenn etwas schiefgeht. throw { name: message: at: text: };
'SyntaxError', m, at, text
}, next = function (c) { // Wird ein c-Parameter übergeben, prüfen, ob er mit dem aktuellen Zeichen übereinstimmt. if (c && c !== ch) { error("Expected '" + c + "’ instead of '" + ch + "'"); } // Nächstes Zeichen abrufen. Sind keine weiteren Zeichen vorhanden, // wird ein Leer-String zurückgegeben.
Max. Linie
ch = text.charAt(at); at += 1;
Ein JSON-Parser This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
151
Links return ch; }, number = function () { // Einen Zahlenwert parsen. var number, string = ''; if (ch === '-') { string = '-'; next('-'); } while (ch >= '0' && ch <= '9') { string += ch; next(); } if (ch === '.') { string += '.'; while (next() && ch >= '0' && ch <= '9') { string += ch; } } if (ch === 'e' || ch === 'E') { string += ch; next(); if (ch === '-' || ch === '+') { string += ch; next(); } while (ch >= '0' && ch <= '9') { string += ch; next(); } } number = +string; if (isNaN(number)) { error("Bad number"); } else { return number; } }, string = function () { // Einen String-Wert parsen. var hex, i, string = '', uffff;
Max. Linie 152
|
Anhang E: JSON
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts // Beim Parsing von String-Werten müssen wir auf " und \ achten. if (ch === '"') { while (next()) { if (ch === '"') { next(); return string; } else if (ch === '\\') { next(); if (ch === 'u') { uffff = 0; for (i = 0; i < 4; i += 1) { hex = parseInt(next(), 16); if (!isFinite(hex)) { break; } uffff = uffff * 16 + hex; } string += String.fromCharCode(uffff); } else if (typeof escapee[ch] === ’string’) { string += escapee[ch]; } else { break; } } else { string += ch; } } } error("Bad string"); }, white = function () { // Whitespace überspringen. while (ch && ch <= ' ') { next(); } }, word = function () { // true, false oder null.
Max. Linie
switch (ch) { case ’t’: next('t'); next('r'); next('u'); next('e'); return true;
Ein JSON-Parser This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
153
Links case 'f': next('f'); next('a'); next('l'); next('s'); next('e'); return false; case 'n': next('n'); next('u'); next('l'); next('l'); return null; } error("Unexpected '" + ch + "'"); }, value,
// Platzhalter für value-Funktion.
array = function () { // Einen Array-Wert parsen. var array = []; if (ch === '[') { next('['); white(); if (ch === ']') { next(']'); return array; // Leeres Array } while (ch) { array.push(value()); white(); if (ch === ']') { next(']'); return array; } next(','); white(); } } error("Bad array"); }, object = function () { // Einen Objektwert parsen. var key, object = {};
Max. Linie 154
|
Anhang E: JSON
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts if (ch === '{') { next('{'); white(); if (ch === '}') { next('}'); return object; // Leeres Objekt } while (ch) { key = string(); white(); next(':'); object[key] = value(); white(); if (ch === '}') { next('}'); return object; } next(','); white(); } } error("Bad object"); }; value = function () { // Einen JSON-Wert parsen. Das kann ein Objekt, ein Array, ein String, eine Zahl // oder ein Wort sein. white(); switch (ch) { case '{': return object(); case '[': return array(); case '"': return string(); case '-': return number(); default: return ch >= '0' && ch <= '9' ? number() : word(); } }; // json_parse-Funktion zurückgeben. Diese hat Zugriff auf alle // obigen Funktionen und Variablen. return function (source, reviver) { var result;
Max. Linie
text = source; at = 0;
Ein JSON-Parser This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
155
Links ch = ' '; result = value(); white(); if (ch) { error("Syntax error"); } // // // // //
Gibt es eine reviver-Funktion, gehen wir die neue Struktur rekursiv durch und übergeben jedes Name/Wert-Paar an die reviver-Funktion zur möglichen Transformation, beginnend bei einem temporären boot-Objekt, das das Ergebnis in einem leeren Schlüssel vorhält. Gibt es keine reviver-Funktion, geben wir einfach das Ergebnis zurück. return typeof reviver === 'function' ? function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); }({'': result}, '') : result; };
}();
Max. Linie
Max. Linie 156
|
Anhang E: JSON
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
First
Index
Hier Mini IVZ eingeben!
Erstellen auf den Arbeitsseiten (siehe Muster)
Abstand untere Tabellenlinie zu Textanfang 1,8 cm -> also: manuell auf den Arbeitsseiten ziehen!!!
Symbole
A
-- Dekrementoperator 120, 127, 132 - Negationsoperator 132 -- Operator, Verwechslungen mit Plus und Minus 132 - Subtraktionsoperator 132 != Operator 117, 131 !== Operator 117 & UND 121 && Operator 19 + Operator 18, 112 Verwechslungen mit Plus und Minus 132 ++ Inkrementoperator 120, 127, 132 Verwechslungen mit Plus und Minus 132 += Operator 16 << Linksshift 121 = Operator 16, 131 == Operator 114, 117, 131 === Operator 16, 114, 117 >> Rechtsshift mit Vorzeichen 121 >>> Rechtsshift ohne Vorzeichen 121 ? Ternäroperator 17 [ ] Postfix-Indexoperator 65 \ Escape-Zeichen 10 ^ XODER 121 | ODER 121 || Operator 19, 24 ~ nicht 121 Operator 18 * * Form von Block-Kommentaren 7 Kommentare 7
adsafe-Option (JSLint) 126 Anweisungen 11-16 ausführbare 11 Blöcke 12 break 14 case-Klausel 13 catch-Klausel 15 do 11, 15 for 11, 14 for...in 14 gelabelte 16 if 13 return 16 Schleife 16 Schleifen 14 switch 11, 13, 16 then-Block 13 throw 15 try 15 var, Funktionen 11 while 11, 14 arguments-Array 114 array.concat( )-Methode 85 array.join( )-Methode 85 array.pop( )-Methode 86 array.prototype 67 array.push( )-Methode 86 array.reverse( )-Methode 86 array.shift( )-Methode 86 array.slice( )-Methode 87 array.sort( )-Methode 87-89 array.splice( )-Methode 89-91 array.unshift( )-Methode 91 Arrayliterale 19
⁄ ⁄
⁄⁄
Max. Linie
⁄
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
157
Links Arrays 63-70, 113 Arrays von Arrays 69 Aufzählungen 66 delete-Operator 65 Dimensionen 68 Elemente von 64 length-Eigenschaft 64 Literale 63 Methoden 67 neue Elemente anhängen 65 Object.create-Methode 68 splice-Methode 65 typeof-Operator 66 undefinierter Wert 68 Verwirrung 66 Zellen einer leeren Matrix 69 Aufrufe 102 Aufrufoperator 19, 31 Aufzählung 26 Ausdrücke 16-19 ? Ternäroperator 17 Aufruf 17 fest eingebauter Wert 16 Infixoperator 17 literaler Wert 16 Operatorvorrang 17 Spezifizierung 17 Spezifizierungsausdruck mit vorangestelltem delete 16 Variablen 16 von Klammern umschlossen 16 vorangestellter Präfixoperator 16 Ausdrucksanweisungen 129 ausführbare Anweisungen 11 Ausnahmen 35
B bitorientierte Operatoren 121, 132 bitwise-Option (JSLint) 126 Blöcke 12, 129 blockfreie Anweisungen 120 Block-Geltungsbereich 40, 106 Block-Kommentare 7, 104 Boolesche Werte 22 break-Anweisung 14, 132 browser-Option (JSLint) 126
Max. Linie 158
|
Index
C Callbacks 44 cap-Option (JSLint) 126 case-Klausel 13 Casting 51 catch-Klausel 15 character-Typ 10 Closure 41-44 constructor-Eigenschaft 52 continue-Anweisung 119, 132 curry-Methode 48
D dauerhaftes Objekt 61 debug-Option (JSLint) 126 deentityify-Methode 45 Delegation 25 delete-Operator 27, 65 differentielle Vererbung 56 do-Anweisung 11, 15 Document Object Model (DOM) 38
E ECMAScript-Sprachspezifikation 124 einzeilige Kommentare 104 eqeqeq-Option (JSLint) 126 Escape-Sequenzen 11 Escape-Zeichen 10 eval-Funktion 118, 132 Sicherheitsprobleme 149 evil-Option (JSLint) 127
F Fakultät 39, 50 fest eingebauter Wert 16 Fibonacci-Zahlen 49 Fließkommazahlen 112 for...in-Anweisung 14, 129 Objekte 26 for-Anweisungen 11, 14, 128 forin-Option (JSLint) 127 fragment-Option (JSLint) 127 function.apply( )-Methode 91 funktionales Muster (Vererbung) 57-61 Funktionen 20, 29-50, 125 allgemeines Muster eines Moduls 46 Aufruf 31-34 new-Präfix 33
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Aufruf über Konstruktor 52 Aufrufoperator 31 Ausnahmen 35 Callbacks 44 Closure 41-44 curry-Methode 48 Geltungsbereich 40 Kaskaden 47 Literale 30 Memoization 48 Module 44-47 Objekte 29 rekursive 37-39 Document Object Model (DOM) 38 Fibonacci-Zahlen 49 Tail Recursion-Optimierung 39 Türme von Hanoi 37 return-Anweisung 35 that produziert Objekte 57 Typen erweitern 36 var-Anweisungen 11 Funktionsanweisung verglichen mit Funktionsausdruck 121 Funktionsaufruf 102 Funktionskonstruktor 52, 119 Funktionsobjekt, wenn Objekt erzeugt wird 52
G
Max. Linie
gelabelte Anweisung 16 Geltungsbereich 12, 40, 109 geschweifte Klammern 12, 103 Gleichheitsoperatoren 117 globale Deklarationen 125 globale Variablen 27, 104, 108, 125 global-Objekt 150 glovar-Option (JSLint) 127 Grammatik 6-21 Anweisungen (siehe Anweisungen) Ausdrücke (siehe Ausdrücke) Funktionen 20 Literale 19 Namen 7 Objektliterale 19 Regeln zur Interpretation der Diagramme 6 Strings 10 length-Eigenschaft 11 Unveränderlichkeit 11
Whitespace 6 Zahlen 8 Methoden 9 negative 9 guter Stil 102
H hasOwnProperty-Methode 26, 114, 116 HTML <script>-Tags (JSON) 147 innerHTML-Eigenschaft 150 JSLint 134
I if-Anweisungen 13, 128 impliziert global 109 inherits-Methode 54 innerHTML-Eigenschaft 150 Instanzen, erzeugen 53 Interpunktionszeichen oder Operatoren 128
J JavaScript analysieren 3-5 Standard 4 warum nutzen 2 JavaScript Object Notation (siehe JSON) JSLint 4, 124-135 -- Dekrementoperator 132 Verwechslung mit Plus und Minus 132 - Operator, Verwechslung mit Plus und Minus 132 != Operator 131 + Operator, Verwechslung mit Plus und Minus 132 ++ Inkrementoperator 132 Verwechslung mit Plus und Minus 132 = Operator 131 == Operator 131 Ausdrucksanweisung 129 bitorientierte Operatoren 132 Blöcke 129 break-Anweisungen 132 continue-Anweisungen 132 eval-Funktion 132 for...in-Anweisungen 129
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Index
|
159
Max. Linie
Links for-Anweisungen 128 Funktionen 125 Funktionsbericht 134 globale Deklarationen 125 globale Variablen 125 HTML 134 if-Anweisungen 128 JSON 134 Komma-Operator 128 Konstruktorfunktionen 133 Labels 132 Member 126 new-Präfix 133 Optionen 126 reguläre Ausdrücke 133 return-Anweisungen 132 Semikola 127 switch-Anweisungen 130 throw-Anweisungen 132 var-Anweisungen 130 Variablen 125 void 133 with-Anweisungen 130 wo zu finden 125 Zeilenumbrüche 128 Zuweisungsanweisung 131 JSON 134 JSON (JavaScript Object Notation) 4, 146-150 -Zeichen 147 Array 147 eval-Funktion 149 HTML <script>-Tags 147 innerHTML-Eigenschaft 150 JSLint 134 Objekt 146 sicher nutzen 149 String 147 Syntax 146-149 Textbeispiel 149 Zahlen 147 JSON.parse-Methode 150 ⁄
K
Max. Linie
K&R-Stil 103 Kaskaden 47 Kleene, Stephen 71 Komma-Operator 128 Kommentare 7, 103
160
|
Index
Konstruktoren 33 definieren 52 Konstruktorfunktionen 133 new-Präfix, vergessen 54 Risiken 54
L Label 132 laxbreak-Option (JSLint) 127 Leerstring 13 length-Eigenschaft (Arrays) 64
M Math-Objekt 9 mehrere Anweisungen 102 Memoization 48 message-Eigenschaft 15 Methoden 85-100 array.concat( ) 85 array.join( ) 85 array.pop( ) 86 array.push( ) 86 array.reverse( ) 86 array.shift( ) 86 array.slice( ) 87 array.sort( ) 87-89 array.splice( ) 89-91 array.unshift ( ) 91 Arrays 67 function.apply( ) 91 mit regulären Ausdrücken arbeitende 71 number.toExponential( ) 92 number.toFixed( ) 92 number.toPrecision( ) 92 number.toString( ) 93 object.hasOwnProperty( ) 93 regex.exec( ) 71, 93 regex.test( ) 71, 95 string.charAt( ) 95 string.charCodeAt( ) 96 string.concat( ) 96 String.fromCharCode( ) 100 string.indexOf( ) 96 string.lastIndexOf( ) 96 string.localeCompare( ) 96 string.match( ) 71, 97 string.replace( ) 71, 97 string.search( ) 71, 98
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts string.slice( ) 99 string.split( ) 71, 99 string.substring( ) 100 string.toLocaleLowerCase( ) 100 string.toLocaleUpperCase( ) 100 string.toLowerCase( ) 100 string.toUpperCase( ) 100 method-Methode 54 Module 44-47 generelles Muster 46 my-Objekt 58
Prototyp 25 Link 25 Referenzen 24 Reflexion 26 undefiniert 23, 25 Werte abrufen 23 Werte aktualisieren 24 Objektliterale 19, 64 Objekt-Specifier 55 on-Option (JSLint) 127 Operator-Vorrang 17
N
P
name-Eigenschaft 15 Namen 7 NaN (not a number) 8, 9, 13, 16, 112 negative Zahlen 9 Newline 80 new-Operator 16, 52, 122, 133 Funktionen 33 vergessen einzufügen 54 nomen-Option (JSLint) 127 null 13, 16, 22, 114 number.toExponential( )-Methode 92 number.toFixed( )-Methode 92 number.toPrecision( )-Methode 92 number.toString( )-Methode 93 numbers-Objekt 64
parseInt-Funktion 111 passfail-Option (JSLint) 127 pi als einfache Konstante 106 plusplus-Option (JSLint) 127 Pratt, Vaughn 105 private Methoden 59 privilegierte Methoden 59 problematische Features von Javascript 108-123 + Operator 112 Arrays 113 bitorientierte Operatoren 121 blockfreie Anweisungen 120 continue-Anweisungen 119 einzelne Anweisungen 120 eval-Funktion 118 Falsch-Werte 114 Fließkommazahlen 112 Funktionsanweisung verglichen mit Funktionsausdruck 121 Geltungsbereich 109 Gleichheitsoperatoren 117 globale Variablen 108 hasOwnProperty-Methode 114 Inkrement- und Dekrementoperatoren 120 NaN (not a number) 112 new-Operator 122 Objekte 115 parseInt-Funktion 111 reservierte Wörter 110 Semikola 109 String-Argument-Form 119 switch-Anweisungen 119 typeof-Operator 111
O
Max. Linie
Object.create-Methode 58, 68 object.hasOwnProperty( )-Methode 93 Object.prototype 67 Objekte 22-28, 115 || Operator 24 Aufzählung 26 dauerhafte 61 Definition 22 Delegation 25 delete-Operator 27 Eigenschaft in der prototype-Kette 26 Eigenschaften 23 for...in-Anweisungen 26 Funktionen 29 globale Variablen 27 hasOwnProperty-Methode 26 Literale 22 neue anlegen 25
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Index
Max. Linie |
161
Links typisierte Wrapper 122 Unicode 110 void 123 with-Anweisungen 118 Programmiertool (siehe JSLint) prototype-Eigenschaft 52 Prototypen grundlegender Typen 37 prototypische Vererbung 4 prototypische Vererbung, Sprache mit 32 prototypisches Muster 55 Pseudoklasse, anlegen 53 pseudoklassisches Muster (Vererbung) 52-55, 59
R Reflexion 26 regex.exec( )-Methode 71, 93 regex.test( )-Methode 71, 95 RegEx-Objekte, Eigenschaften 78 reguläre Ausdrücke 71-84, 133 $-Zeichen 75, 76 (...) 74 (?!-Präfix 82 (?:...)? 77 (?:-Präfix 82 (?=-Präfix 82 ?-Zeichen 74 \1-Zeichen 81 \b-Zeichen 80, 81 \d 77 \D-Zeichen 80 \d-Zeichen 80 \f-Zeichen 80 \n-Zeichen 80 \r-Zeichen 80 \S-Zeichen 81 \s-Zeichen 81 \t-Zeichen 80 \u-Zeichen 80 \W-Zeichen 81 \w-Zeichen 81 ^-Zeichen 74, 76 -Zeichen 74 Backslash-Zeichen 80-81 Carriage Return-Zeichen 80 einfache Zeichenklasse 81 einfangende Gruppe 74, 82 Elemente 78-84 regex-auswahl 79 regex-escape 80-81 ⁄
Max. Linie
162
|
Index
regex-faktor 80, 84 regex-gruppe 82 regex-klasse 82 regex-klassen-escape 83 regex-quantifier 84 regex-sequenz 79 Flags 78 Formfeed-Zeichen 80 Konstruktion 77-78 mir Regex arbeitende Methoden 71 negative Lookahead-Gruppe 82 Newline-Zeichen 80 nicht einfangende Gruppe 74, 82 null oder einmal wiederholen 74 optionale Gruppe 74 optionale nicht einfangende Gruppe 77 positive Lookahead-Gruppe 82 schluderig 75 Syntaxdiagramme 73 Tab-Zeichen 80 Unicode-Zeichen 80 URLs erkennen 72-77 Ziffern erkennen 77 Rekursion 37-39 Document Object Model (DOM) 38 Fibonacci-Zahlen 49 Tail Recursion (Optimierung) 39 Türme von Hanoi 37 reservierte Wörter 8, 110 return-Anweisungen 16, 35, 132 rhino-Option (JSLint) 127
S says-Methode 59 Schleifenanweisung 14, 16 schöne Features 105-107 schwach typisierte Sprache 51 Semikola 109, 127 seqer-Objekt 46 setInterval-Funktion 119 setTimeout-Funktion 119 Simplified JavaScript 105 spec-Objekt 58 splice-Methode (Arrays) 65 Sprache, Struktur (siehe Grammatik) Stil 101-104 Aufruf 102 Block-Kommentare 104 geschweifte Klammern 103 globale Variablen 104
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts guter 102 K&R 103 Kommentare 103 mehrere Anweisungen 102 strukturierte Anweisungen 103 switch 104 Zeilenkommentare 104 String, vergrößern mit deentityifyMethode 45 string.charAt( )-Methode 95 string.charCodeAt( )-Methode 96 string.concat( )-Methode 96 String.fromCharCode( )-Methode 100 string.indexOf( )-Methode 96 string.lastIndexOf( )-Methode 96 string.localeCompare( )-Methode 96 string.match( )-Methode 71, 97 string.replace( )-Methode 71, 97 string.search( )-Methode 71, 98 string.slice( )-Methode 99 string.split( )-Methode 71, 99 string.substring( )-Methode 100 string.toLocaleLowerCase( )-Methode 100 string.toLocaleUpperCase( )-Methode 100 string.toLowerCase( )-Methode 100 string.toUpperCase( )-Methode 100 Stringargument-Form 119 Stringliteral 10 Strings 10, 22 leere 13 length-Eigenschaft 11 unveränderliche 11 Struktur der Sprache (siehe Grammatik) strukturierte Anweisungen 103 superior-Methode 60 Supermethoden 54 switch-Anweisung 11, 13, 16, 104, 119, 130 Syntaxdiagramme 73, 136-145 Syntaxprüfung (siehe JSLint)
T
Max. Linie
Tail Recursion (Optimierung) 39 Testumgebung 5 then-Block 13 this-Schlüsselwort 54 Thompson, Ken 71 throw-Anweisungen 15, 132 Top Down-Parser 105 trim-Methode 37 try-Anweisungen 15
Türme von Hanoi 37 TypeError-Ausnahme 24 Typen 22 Prototypen 37 Typen erweitern 36 typeof-Operator 18, 66, 111, 113 typisierte Wrapper 122
U undefiniert 8, 13, 16, 22, 23, 25, 31, 35, 68, 114 undef-Option (JSLint) 127 Unendlichkeit 8, 9, 16 Unicode 110
V var-Anweisungen 130 Funktionen 11 Variablen 16, 125 Vererbung 4, 51-62 differentielle 56 funktionales Muster 57-61 Objekt-Specifier 55 prototypisches Muster 55 pseudoklassisches Muster 52-55, 59 Teile 61-62 Verifier (siehe JSLint) Verkettung 112 void-Operator 123, 133
W while-Anweisungen 11, 14 white-Option (JSLint) 127 Whitespace 6 widget-Option (JSLint) 127 Wilson, Greg 105 with-Anweisungen 118, 130 Wrapper, typisierte 122
Z Zahlen 8, 22 Methoden 9 negative 9 Zahlenliteral 9 Zeilen abschließende Kommentare 7 Zeilenumbrüche 128 Zuweisung 131 Zuweisungsanweisung 131
This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Index
Max. Linie |
163
Rechts Über den Autor Douglas Crockford ist Senior JavaScript-Architect bei Yahoo! und hat sich durch sein langjähriges Engagement rund um JavaScript einen Namen gemacht. Er ist der Entwickler des JSON-Formats und gilt unter JavaScript-Programmierern als echte Autorität. Douglas spricht regelmäßig auf Konferenzen über fortgeschrittene Techniken der Webprogrammierung und außerdem ist er Mitglied im ECMAScriptKomitee.
Kolophon Der Schmetterling auf dem Umschlag von Das Beste an JavaScript ist ein Afrikanischer Monarch (Danaus chrysippus), der auch als Gewöhnlicher Tiger oder (im englischsprachigen Raum) als African Queen bekannt ist. Er gehört zur Familie der Edelfalter (Nymphalidae) und kann eine Flügelspannweite von bis zu 80 mm aufweisen. Das ursprüngliche Verbreitungsgebiet des Afrikanischen Monarchen liegt in Afrika (südlich der Sahara), im arabischen Raum, im tropischen Asien und in Australien. In Europa findet man ihn auf den Kanarischen Inseln und als Einwanderer auch im Mittelmeerraum. Da der Falter Wanderungen vornimmt, kommt er auch in Gebieten vor, in denen er nicht bodenständig ist. Der Afrikanische Monarch hat hellorangefarbene Flügel, auf denen sich mehrere schwarze Flecken finden, und eine schwarz-weiße Randbinde. Sein Körper ist schwarz mit weißen Flecken, sein Hinterleib ist weiß. Obwohl der Afrikanische Monarch vor allem durch seine Schönheit hervorsticht, ist er für Fressfeinde ungenießbar. Im Larvenstadium ernährt er sich von giftigen Pflanzen, deren toxische Substanzen er speichert. Der Verzehr der Schmetterlinge löst bei Vögeln Brechreize aus, die tödlich sein können. Der Umschlagsentwurf dieses Buches basiert auf dem Reihenlayout von Edie Freedman und stammt von Emma Colby, die hierfür einen Stich des Dover Pictorial Archive aus dem 19. Jahrhundert verwendet hat. Das Coverlayout der deutschen Ausgabe wurde von Michael Oreal unter Verwendung der Schriftart ITC Garamond von Adobe erstellt. Als Textschrift verwenden wir die Linotype Birka, die Überschriftenschrift ist die Adobe Myriad Condensed und die Nichtproportionalschrift für Codes ist LucasFont’s TheSans Mono Condensed.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.
Links
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2008 O’Reilly & Associates, Inc. All rights reserved.