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
>=
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: '' }; // 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( '
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 ? @ [ \ ] ^ _ ` { | } ~
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!'; var tags = /[^]+|/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!'; var tags = /[^]+|/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: & | ^ ~ >> >>>
|| && === !== = >>> >>>=
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