[Dieser Blogpost ist die Langversion eines Artikels im CHIP Web Design 2013.]
JavaScript ist eigentlich eine recht kompakte Sprache. Wenn es nur nicht all diese Fallgruben gäbe...
Dieser Artikel erklärt die 12 größten und wie man am besten mit ihnen umgeht. Zur Lektüre werden grundlegende JavaScript-Kenntnisse vorausgesetzt. Wir halten uns an die aktuelle Version von JavaScript, ECMAScript 5.
Anmerkung: Da Oracle, als Erbe von Sun, ein Trademark auf den Begriff „Java“ hat und dieses nur an Mozilla lizensiert, dürfen nur diese beiden Firmen offiziell den Begriff „JavaScript“ verwenden. Inoffiziell hindert das keinen, die Sprache so zu nennen, aber für den offiziellen Sprachstandard musste ein anderer Name gefunden werden. Dieser Name ist ECMAScript, nach der Organisation Ecma, die den Standard verwaltet. Die aktuelle Version von JavaScript ist ECMAScript 5 und existiert seit Dezember 2009, die nächste Version, ECMAScript 6, wird Ende 2013 fertig werden. Komplette Unterstützung wird danach etwas dauern, aber Teile wird man schon davor ausprobieren können.
Die folgenden Abschnitte behandeln je eine Fallgrube.
Am Ende wird ein Ausblick auf ECMAScript 6 gegeben, das viele der Fallgruben eliminiert.
Fallgrube: implizite Umwandlungen von Werten
JavaScript ist sehr tolerant beim Annehmen von Werten. Überall wo z.B. eine Zahl erwartet wird, weist die Sprache Werte anderer Typen nicht zurück, sondern versucht sie in Zahlen umzuwandeln. Zum Beispiel:
Der Plus-Operator (+) funktioniert etwas anders und kann sowohl mit Zahlen als auch mit Zeichenketten umgehen. Das führt zu einer anderen Art von Problem, wie wir gleich sehen werden.
Das automatische Umwandeln nach Boolean ist meistens eher praktisch, wir befassen uns aber damit, weil es Wissensgrundlage für spätere Themen ist. Eine echte Fallgrube ist hingegen, wie Werte von String-Werte umgewandelt.
Implizite Umwandlung nach Boolean: „truthy“ und „falsy“ Werte
Wenn Wahrheitswerte erwartet werden, z.B. für die Bedingung einer if-Anweisung, kann man beliebige Werte übergeben, die entweder als true oder false interpretiert werden. Die folgenden Werte werden als false interpretiert:
undefined, null (siehe auch Fallgrube 2)
Boolean: false
Number: -0, +0, NaN
String: ''
Alle anderen Werte gelten als true. Im Englischen hat sich der Begriff „falsy“ (in etwa: „fälschlich“, im Sinne von „bläulich“) für Werte etabliert, die äquivalent zu false sind und „truthy“ für wahr-äquivalente Werte. Den interaktiven Test, welcher Wert wie interpretiert wird, kann man über die Funktion Boolean machen, die ihr Argument in einen Wahrheitswert umwandelt:
Überraschend ist z.B., dass wirklich nur der leere String falsy ist:
Implizite Umwandlung von Strings
In der Webentwicklung kommt es öfters mal vor, dass man als String erhält, was eigentlich eine Zahl oder ein Wahrheitswert ist. Zum Beispiel, wenn Benutzer diese Art von Daten in ein Formular eingeben. Vergisst man, diese Strings explizit umzuwandeln, dann überrascht einen JavaScript in zweierlei Hinsicht negativ: Erstens wird man nicht gewarnt. Zweitens wird automatisch umgewandelt, aber falsch.
Die Addition (+) ist z.B. riskant, da sie Strings aneinanderhängt, sobald einer der Operanden ein String ist. Im folgenden denkt man, man addiert 1 zu 5. In Wahrheit fügt man aber die Zeichenketten '5' und '1' zusammen:
Ausserdem gibt es ein paar Werte, die falsy sind, nach der Umwandlung zu String aber truthy werden. Das untersuchen wir mit Hilfe der folgenden Funktion genauer.
Beispiel: false.
Beispiel: undefined.
Implizite Umwandlung von Objekten
Eine implizite Umwandlung von Objekten findet nur statt, wenn eine Zahl oder eine Zeichenkette erwartet wird. Im ersten Fall findet die Umwandlung in drei Schritten statt:
Rufe die Methode valueOf() auf. Ist das Ergebnis primitiv (kein Objekt), verwende es und wandle es in eine Zahl um.
Rufe andernfalls die Methode toString() auf. Ist das Ergebnis primitiv, verwende es und wandle es in eine Zahl um.
Wirf andernfalls einen TypeError.
Beispiel für Schritt 1:
Beispiel für Schritt 3:
Im zweiten Fall, wenn eine Zeichenkette erwartet wird, sind Schritt 1 und 2 vertauscht: zuerst wird toString() aufgerufen, dann valueOf().
Fallgrube: zwei „Nicht-Werte“ undefined und null
Die meisten Programmiersprachen haben nur einen Wert für „kein Wert“ bzw. „leer“. In Java ist das z.B. null. JavaScript hat unnötigerweise zwei dieser speziellen Werte: undefined und null.
undefined wird von der Sprache selbst zugewiesen. Variablen, die noch nicht initialisiert wurden, haben diesen Wert.
undefined wird ebenso für nicht übergebene Parameter verwendet:
null wird wenn, dann von Programmierern verwendet, z.B. um anzugeben, dass ein Wert fehlt.
Check: hat eine Variable einen Wert?
Will man wissen, ob eine Variable v einen Wert hat, muss man also sowohl auf undefined als auch auf null prüfen. Glücklicherweise sind beide Werte truthy. Mit einer Überprüfung auf Truthiness per if schlägt man also zwei Fliegen mit einer Klappe:
Einziges Manko: auch false, -0, +0, NaN und '' werden als „kein Wert“ interpretiert. Wenn einem das nicht Recht ist, kann man diese kompakte Art der Überprüfung nicht verwenden. Sie ist aber trotzdem sehr beliebt, u.a. zur Parameterbehandlung. In Abschnitt 5 sind hierzu Beispiele zu sehen.
Fallgrube: die normale Gleicheit (==)
Eine Grundregel gleich vorweg: Die normalen Gleichheitsoperatoren == und != sind so problematisch, dass man immer die strengen Gleichheitsoperatoren === und !== verwenden sollte – ohne Ausnahme (auch wenn man manchmal gegenteiliges liest). Mit dieser einfachen Regel im Kopf können wir uns nun in Ruhe ansehen, was an == seltsam ist.
Der normale Gleichheitsoperator (==) hat viele Macken. Er ist zwar tolerant, aber es gelten nicht die üblichen Regeln für truthy und falsy:
Ausserdem lassen sich Werte vergleichen, die man eigentlich nicht vergleichen kann:
Details zu == können Sie hier nachlesen: 2ality.com/2011/06/javascript-equality.html
Bei der strengen Gleichheit (===) können Werte unterschiedlichen Typs nie gleich sein, weshalb keines der oben genannten Probleme auftritt.
Fallgrube: unbekannte Variablennamen erschaffen globale Variablen
Normalerweise legt JavaScript automatisch globale Variablen an, wenn man einen unbekannten Variablennamen verwendet:
Im ECMAScript 5 Strict Mode wird dankenswerterweise gewarnt:
Fallgrube: Parameterbehandlung
Die grundlegende Parameterbehandlung ist einfach, für fortgeschrittene Aufgaben wird allerdings Handarbeit benötigt. Es gibt zwei wichtige Grundregeln zur Parameterbehandlung.
Grundregel 1: Einer Funktion können beim Aufruf beliebig viele Argumente übergeben werden, egal wie viele Parameter in der Funktionsdefinition stehen. Jedem fehlenden Parameter wird der Wert undefined gegeben. Argumente, die zu viel sind, werden ignoriert.
Lassen Sie uns beispielsweise von folgender Funktion ausgehen:
Diese Funktion kann man mit beliebig vielen Argumenten aufrufen:
Grundregel 2: Alle übergebenen Parameter sind über die Array-ähnliche Variable arguments zugänglich. Mit folgender Funktion können wir uns ansehen, wie sie funktioniert.
Die Funktion im Einsatz:
arguments ist immer vorhanden, egal wie viele Parameter explizit spezifiziert wurden. Es enthält immer alle Argumente.
Wurde ein Parameter übergeben?
Wenn ein Aufrufer einen Parameter nicht angibt, wird der aufgerufenen Funktion undefined übergeben. Dadurch, dass undefined falsy ist, kann man eine if-Anweisung verwenden, um zu überprüfen, ob ein Parameter existiert oder nicht:
Folglich bekommt man das selbe Ergebnis, wenn man einen Parameter weglässt und wenn man undefined übergibt:
Für alle truthy Werte funktioniert der Test ebenfalls bestens:
Allein bei anderen falsy Werten muss man aufpassen. So werden z.B. false und der leere String als fehlende Parameter gewertet:
Dennoch hat sich dieses Muster bewährt. Man muss zwar ein klein wenig aufpassen, der Code ist aber angenehm kompakt und es ist egal, ob man undefined oder null verwendet.
Standardwerte für Parameter
Die folgende Funktion soll man mit 0 bis 2 Parametern aufrufen können. x und y sollten den Wert 0 bekommen, wenn sie nicht angegeben werden.
Interaktion:
Manchmal sieht man auch eine Variante, die den “Oder”-Operator (||) verwendet. Diesen Operator schreibt man wie folgt.
Das Ergebnis ist x, wenn x truthy ist, andernfalls y. Beispiele:
Damit kann man Standardwerte für Parameter auch wie folgt zuweisen:
Eine variable Anzahl von Parametern
arguments kann man auch verwenden, um eine variable Anzahl von Parametern zuzulassen. Ein Beispiel ist die folgende Funktion format, die der klassischen C-Funktion sprintf nachempfunden ist:
Das erste Argument ist ein Muster, in dem die zwei Zeichen '%s' Lücken kennzeichnen, in die die darauffolgenden Werte eingesetzt werden. Eine einfache Implementierung von format sieht wie folgt aus.
Beachten Sie: In der Schleife wird der nullte Parameter, pattern, übersprungen und mit dem ersten Parameter danach begonnen.
Eine bestimmte Anzahl von Parametern erzwingen
Will man den Aufrufer zwingen, eine bestimmte Anzahl von Parametern zu verwenden, bleibt einem nur eine Überprüfung von arguments.length, zur Laufzeit:
arguments ist kein Array
arguments ist kein Array, es ist nur Array-ähnlich: Man kann zwar mit arguments[i] auf den i-ten Parameter zugreifen und per arguments.length abfragen, wie viele Parameter es gibt. Aber Array-Methoden wie forEach und indexOf stehen nicht zur Verfügung. Weitere Details werden bei Fallgrube 9 erklärt.
Fallgrube: der Geltungsbereich von Variablen
Üblicherweise gelten Variablen in Programmiersprachen nur innerhalb des Blockes, in dem sie deklariert wurden. In JavaScript gelten sie in der gesamten umgebenden Funktion:
Tatsächlich findet bei einer Variablendeklaration „Hoisting“ (Anheben) statt: Die Deklaration wird an den Anfang der Funktion geschoben (eine initialisierende Zuweisung aber nicht). Sprich: die obenstehende Funktion sieht intern wie folgt aus.
Über einen Trick kann man in JavaScript aber den Geltungsbereich einer Variablen auf einen Block beschränken:
Hier wurde im Inneren der if-Anweisung eine Funktion definiert und gleich aufgerufen. Damit gilt tmp wirklich nur dort. Beachten Sie, dass die Klammern am Anfang und am Ende zwingend notwendig sind, da erst sie die Funktion zu einem Ausdruck machen. Leider können nur Ausdrücke sofort ausgeführt werden. Eine derart eingesetzte Funktion heißt „IIFE“ (ausgesprochen: „Iffi“), von englisch „Immediately Invoked Function Expression“ (sofort aufgerufener Funktionsausdruck).
Fallgrube: Closures und freie (externe) Variablen
Ein sehr mächtiges Feature von JavaScript sind Closures (deutsch: Abschlüsse): Wenn eine Funktion den Ort verlässt, wo sie erschaffen wurde, dann hat sie immer noch Zugriff auf die Variablen, die zu ihrer Geburt existierten.
Beispielsweise:
Hier hat die innere Funktion (*) während ihrer gesamten Lebenszeit Zugriff auf die Variablen start und step. Es wird also nicht nur die Funktion zurückgegeben, sondern eine Kombination aus der Funktion und den Variablen start und step. Die Datenstruktur, in der die beiden Variablen gespeichert sind, heißt Environment (deutsch Umgebung). Ein Environment ist sehr ähnlich zu einem Objekt und wird in der Funktion abgelegt, was die Funktion zur Closure macht. Der Name erklärt sich daher, dass das Environment die Funktion abschließt: alle Variablen haben jetzt Werte, nicht nur die, die innerhalb der Funktion deklariert wurden. Im Einsatz sieht incrementorFactory wie folgt aus:
Die Fallgrube
Closures bekommen also nicht eine Momentaufnahme, sondern „lebende Variablen“ mit. Das führt im folgenden Code zu Problemen:
Man erwartet vielleicht, dass jede Funktion, die an der Stelle (*) in das Array gestellt wird, den aktuellen Wert von i erhält. Stattdessen bricht die Verbindung zu dem „lebenden“ i nie ab. Und dessen Wert ist nach der Schleife 5. Eine mögliche Lösung des Problems ist, den aktuellen Wert der Variablen i per IIFE (siehe oben) zu kopieren:
Eine weitere Möglichkeit ist, forEach zu verwenden, dort werden in jedem Schleifendurchlauf neue Variablen erzeugt (aufgrund des Funktionsaufrufs):
Fallgrube: Array-ähnliche Objekte
Manche Objekte in JavaScript sehen aus wie Arrays, sind aber keine. Man bezeichnet sie als Array-ähnlich. Array-ähnliche Objekte
haben: indizierten Zugriff auf Elemente und das Property length, das angibt, wie viele Elemente das Objekt enthält.
haben nicht: Array-Methoden wie push, forEach und indexOf.
Das bekannteste Array-ähnliche Objekt ist die spezielle Variable arguments. Man kann die Anzahl der Argumente ermitteln per
Und man kann auf einzelne Argumente zugreifen, z.B. auf das erste Argument:
Array-Methoden muss man sich aber borgen. Das geht, weil die meisten dieser Methoden generisch sind.
Generische Methoden
Eine generische Array-Methode setzt nicht voraus, dass this ein Array ist, sie fordert nur ein Objekt, das indizierten Elementzugriff und length hat. Normalerweise ruft man eine Array-Methode m für ein Array a wie folgt auf:
Alle Funktionen haben eine Methode call, mit der man diesen Aufruf auch anders ausführen kann:
Das erste Argument von call ist der Wert für this, den m erhält. In diesem Fall ist das a. Dadurch dass wir auf m direkt und nicht über a zugreifen haben wir nun die Möglichkeit, ein anderes this zu verwenden, z.B. arguments:
Nun zu einem konkreten Beispiel. Die folgende Funktion printArgs gibt alle Argumente aus, die sie erhält:
Die Funktion im Einsatz:
Will man hingegen arguments verändern, sollte man es als erstes in ein Array umwandeln. Das geht wie folgt:
Vergleiche: Von einem Array a erzeugt man eine Kopie per
Fallgrube: Vererbung zwischen Konstruktoren
In JavaScript verwendet man Konstruktoren, um Typen zu implementieren. Sie sind Objektfabriken und entsprechen lose Klassen in anderen Sprachen.
Die zwei Prototypen
In JavaScript wird der Begriff „Prototyp“ leider doppelt verwendet.
Beziehung zwischen Objekten.
Auf der einen Seite gibt es die Prototypbeziehung zwischen Objekten, durch die ein Objekt alle Propertys seines Prototyps erbt. Intern wird diese Beziehung hergestellt, indem ein Objekt über das interne Property [[Prototype]] auf sein Prototyp-Objekt verweist. Extern kann man dieses Property nicht sehen, aber man kann per Object.create() ein Objekt herstellen, das einen gegebenen Prototyp hat:
Und man kann per Object.getPrototypeOf() den Wert des Propertys auslesen:
Property von Konstruktoren.
Auf der anderen Seite gibt es das Property prototype, das Konstruktoren haben. Dieses hat als Wert ein Objekt, das zum Prototyp aller Instanzen des Konstruktors wird.
Um Missverständnissen vorzubeugen, kann man das Objekt in prototype auch als Instanz-Prototyp bezeichnen.
Alleinstehende Konstruktoren
Einen alleinstehenden Konstruktor K zu implementieren ist einfach: Alle Instanzdaten werden in K dem Objekt in this hinzugefügt. Alle Instanz-übergreifenden Daten (vor allem die Methoden), werden in das Objekt in K.prototype gesteckt.
Ein Beispiel:
aPerson ist eine Instanz des Konstruktors Person. Zwischen den Objekten aPerson und Person.prototype besteht eine Prototypbeziehung.
Die Instanzdaten sind in diesem Fall this.name. Instanzübergreifend steht die Methode describe zur Verfügung. Ein Detail, das gleich noch wichtig wird, ist, dass jeder Instanz-Prototyp mit dem Property constructor auf den Konstruktor zurückverweisen sollte.
Dadurch kann man zu jeder Instanz den Konstruktor finden, z.B. um neue Instanzen anzulegen.
Standardmäßig ist bereits alles richtig konfiguriert:
Sub-Konstruktoren
Will man Vererbung zwischen Konstruktoren einsetzen, wird es um einiges komplizierter. Lassen Sie uns beispielsweise Employee als Sub-Konstruktor von Person implementieren.
Employee ist ein Sub-Konstruktor von Person. Beachten Sie, dass letzterer unverändert bleibt. Employee.prototype erbt durch die Prototypbeziehung alle Methoden von Person.prototype. Die Instanzdaten werden geerbt, indem Employee Person aufruft.
Es gibt folgende Dinge zu beachten:
Der Sub-Konstruktor muss den Super-Konstruktor aufrufen, damit dieser seine Instanzdaten hinzufügen kann. Das geschieht per call-Methode, da der Super-Konstruktor das aktuelle this verwenden muss und kein eigenes Instanzobjekt erzeugen soll.
Der Sub-Prototyp muss vom Super-Prototypen erben, da er auf diesem Weg die Super-Methoden erhält.
Durch Schritt 2 haben wir das Objekt weggeworfen, in dem constructor richtig gesetzt ist. Hier müssen wir nachbessern.
Aufrufe von überschriebenen Methoden (Super-Methoden) sind leider kompliziert. Wir greifen direkt auf die Super-Methode Person.prototype.describe zu und geben ihr per call() das aktuelle this mit.
Eine Hilfsfunktion für Konstruktor-Vererbung
Wenn man all dies nicht händisch machen will, kann man eine Hilfsfunktion programmieren:
Die Funktion copyOwnPropertiesFrom kopiert „eigene“ (nicht geerbte) Propertys von source zu target und verwendet dazu Property-Descriptoren (Mehr Information: 2ality.com/2012/10/javascript-properties.html).
Dank inherits lässt sich der Code für Employee eleganter schreiben:
Angenehmerweise nehmen wir nicht mehr direkt auf den Super-Konstruktor Person bezug, sondern verweisen per Employee._super auf dessen Instanz-Prototypen. Als Folge davon wird leider der Aufruf des Super-Konstruktors an der Stelle (*) etwas länglich.
Lesen und Schreiben von Propertys
Sowohl das Lesen als auch das Schreiben von Propertys kann zu Überraschungen führen. In JavaScript unterscheidet man geerbte Propertys und „eigene“ Propertys eines Objektes obj. Erstere existieren in einem der Prototypen von obj, letztere in obj selbst. Mit Object.keys() kann man die eigenen Propertys eines Objektes ermitteln. Im folgenden Beispiel hat obj das eigene Property foo.
Geerbte Propertys hat es hingegen mehr, unter anderem toString und hasOwnProperty:
Lesen von unbekannten Propertys
Bei dem Lesezugriff
passiert folgendes:
Hat obj ein eigenes Property prop, so wird dessen Wert zurückgegeben. Hat obj ein geerbtes Property prop, so wird dessen Wert zurückgegeben. Andernfalls wird undefined zurückgegeben. Die Fallgrube hierbei ist, dass ein falsch geschriebener Propertyname keine Ausnahme (Exception) auslöst. Stattdessen bekommt man den Wert undefined zurück, der, wenn überhaupt, erst später zu einer Ausnahme führt.
Zuweisen zu unbekannten Propertys
Bei der Zuweisung
passiert folgendes:
Hat obj ein eigenes Property prop, so wird dessen Wert geändert. Andernfalls wird ein neues eigenes Property prop zu obj hinzugefügt und mit einem Wert versehen.
Dabei wird prop selbst dann hinzugefügt, wenn es dieses Property schon in einem der Prototypen von obj gibt. Beispiel: Gegeben sei das Objekt obj, dessen direkter Prototyp proto ist:
Man kann color per obj lesen und obj hat keine eigenen Propertys:
Weist man aber color einen Wert zu, so wird ein eigenes Property angelegt:
proto wurde hierbei nicht verändert:
Die Fallgrube ist, dass man bei Tippfehlern nicht gewarnt wird, sondern einfach ein neues Property angelegt wird.
Diese Art der Zuweisung schützt also Prototyp-Propertys davor, verändert zu werden. Dennoch wird davon abgeraten, Standardwerte dort abzulegen, denn wenn man in Objekte hineingreift, wirkt der Schutz nicht:
Fallgrube: this in echten Funktionen
Funktionen spielen in JavaScript drei Rollen: echte Funktion, Konstruktor, Methode. Bedarf für this besteht nur bei den letzten beiden Rollen. Leider haben aber auch echte Funktionen ihr eigenes this, was zu einer Reihe von Problemen führt.
Fallgrube: this in echten Funktionen in einer Methode
Im folgenden Objekt wird in der Methode printFriends an der Stelle (*) eine echte Funktion eingesetzt.
printFriends funktioniert nicht korrekt:
Das liegt daran, dass die echte Funktion (*) this verwendet (**) und erwartet, dass es sich dabei um das this von printFriends handelt. Tatsächlich verweist this aber auf das „globale Objekt“ (window in Browsern). Das ist immer der Fall, wenn eine Funktion als echte Funktion und nicht als Methode aufgerufen wird:
Da es die globalen Variable name nicht gibt, wird zweimal undefined ausgegeben.
Warnungen per Strict Mode
Wenn man den „strengeren“ Strict Mode von ECMAScript 5 anschaltet, dann hat this in echten Funktionen einen anderen Wert, nämlich undefined.
Daher erhält man nun eine Warnung, wenn man this falsch verwendet. Man muss nur den Strict Mode anschalten, indem man eine Zeile am Anfang einfügt:
Die Warnung sieht so aus:
Lösungen
Es gibt zwei Lösungen. Die üblichere sieht wie folgt aus:
Sprich: Man merkt sich das richtige this in der Variablen that, die nicht von der Funktion an der Stelle (*) überschattet wird. Eine andere Möglichkeit ist, die Methode bind zu verwenden, durch die eine neue Funktion erzeugt wird, die ein festes this hat.
An der Stelle (*) wird eine Funktion erzeugt, deren this immer gleich dem this-Wert von printFriends ist.
Fallgrube: this in extrahierten Methoden
Wenn man eine Methode aus einem Objekt entnimmt, wird es wieder zu einer echten Funktion. Damit reißt die Verbindung zum Objekt ab und die Methode funktioniert meist nicht mehr einwandfrei. Nehmen wir zum Beispiel das folgende Objekt counter:
In JavaScript gibt es viele Funktionen und Methoden, die Callbacks (Rückruffunktionen) erwarten. In Browsern zum Beispiel setTimeout() und Event Handler. Wenn wir counter.inc als Callback übergeben wollen, bekommen wir Probleme. Diese lassen sich am besten demonstrieren, indem wir eine einfache Callback-aufrufende Funktion zu Hilfe nehmen:
Wir verwenden nun callIt, um counter.count aufzurufen. Leider scheint der Aufruf keine Wirkung zu haben:
Das Problem ist, dass counter.inc eine echte Funktion ist, wenn es in callIt aufgerufen wird. Folglich zeigt this wieder auf das globale Objekt und es wurde versucht, die globale Variable count um Eins zu erhöhen. Auch hier können wir bind() einsetzen:
Nun wird callIt mit einer neuen Funktion aufgerufen, deren this den festen Wert counter hat. Damit klappt das Erhöhen von counter.count.
Fallgrube: this und falsch aufgerufene Konstruktoren
Vergisst man bei einem Aufruf eines Konstruktors versehentlich new, so wird er als echte Funktion aufgerufen. Also läuft man erneut Gefahr, globale Variablen zu erzeugen:
Im normalen Einsatz ruft man Point mit new auf:
Vergisst man new, so erhält p den Wert undefined und globale Variablen x und y werden erzeugt:
Auch hier empfiehlt es sich, den Strict Mode zu verwenden. Wir schalten ihn diesmal nur lokal, innerhalb von Point an.
Nun wird man gewarnt, wenn man new vergisst:
Fallgrube: die for-in-Schleife
Die for-in-Schleife kann verwendet werden, um über die Namen der Propertys eines Objektes zu iterieren:
Diese Art der Schleife hat jedoch zwei große Mängel: Erstens wird bei Objekten über alle Propertynamen iteriert, auch über die Namen von geerbten Propertys. Zweitens wird auch bei Arrays über alle Propertynamen iteriert. Also weder über die Elemente (was nützlicher wäre), noch ausschließlich über Indizes. Die folgenden Unterabschnitte erklären genauer, was das bedeutet.
Iterieren über Objekte
Die for-in-Schleife berücksichtigt alle Propertys, also auch geerbte. [Anmerkung: nicht „aufzählbare“ (engl. enumerable) Propertys werden hingegen von for-in ignoriert. Aufzählbarkeit ist eine konfigurierbare Eigenschaft von Propertys. Details können Sie hier nachschlagen: 2ality.com/2012/10/javascript-properties.html ]
Um zu sehen, warum das problematisch sein kann, erstellen wir einen Konstruktor Car für Objekte wie das oben erwähnte modelT.
Dieser Konstruktor wird wie folgt eingesetzt:
Wenn wir nun über die Propertynamen iterieren, so sehen wir auch das geerbte Property toString.
Iterieren über Arrays
Wenn man über Arrays iteriert, will man normalerweise mit den Elementen arbeiten. Leider iteriert for-in über die Indizes und nicht nur über diese, sondern über alle Propertynamen.
Alternativen zu for-in
Die generelle Empfehlung ist, for-in zu vermeiden. ECMAScript 5 bietet bessere Alternativen. Für Arrays kann man die forEach-Methode verwenden:
Für Objekte kann man Object.keys() und forEach() kombinieren:
Fazit und Ausblick
JavaScript hat einige Fallgruben. In diesem Artikel haben wir uns die 12 größten davon angesehen und wie man sie am besten umgeht. Dennoch ist JavaScript weniger komplex als viele gängige Programmiersprachen. Die Fallgruben muss man als Teil der Sprache lernen, das erhöht den Lernaufwand ein wenig, aber nicht viel.
ECMAScript 5 und ältere Browser
In diesem Artikel haben wir ECMAScript-5-Funktionen und Methoden wie Object.keys() und forEach() verwendet. Diese kann man in allen modernen Browsern verwenden. Sollten Sie Rücksicht auf ältere Browser nehmen müssen, haben Sie zwei Möglichkeiten:
Sie können die ECMAScript-5-Funktionalität per Bibliothek nachrüsten.
github.com/kriskowal/es5-shim
Sie können Underscore.js verwenden. Diese Bibliothek enthält vieles der ECMAScript-5-Funktionalität. Kann ein Browser ECMAScript 5, so reicht Underscore die Aufrufe zur Standardbibliothek durch.
underscorejs.org
ECMAScript 6
Hier nochmals die Liste der Fallgruben:
Implizite Umwandlungen von Werten
Zwei „Nicht-Werte“ undefined und null
Die normale Gleicheit (==)
Unbekannte Variablennamen erschaffen globale Variablen
Parameterbehandlung
Der Geltungsbereich von Variablen
Closures und freie (externe) Variablen
Array-ähnliche Objekte
Vererbung zwischen Konstruktoren
Lesen und Schreiben von Propertys
this in echten Funktionen
Die for-in-Schleife
ECMAScript 6 wird viele dieser Fallgruben eliminieren, durch folgende Features:
Mächtige Parameterbehandlung (Fallgrube 5 und teilweise 8, da man arguments nicht mehr benötigen wird).
let-Deklarationen für Variablen mit Block-Gültigkeitsbereich (Fallgrube 6).
Eine vielseitige for-of-Schleife (Fallgrube 12).
Wenn man let in einer der drei for-Schleifen (for, for-in, for-of) verwendet, so wird bei jedem Schleifendurchlauf eine neue Bindung der Schleifenvariable angelegt (Fallgrube 7).
Klassen, die – trotz ihres Namens – nur eine angenehmere Syntax für Konstruktoren sind (Fallgrube 9).
Das Schlüsselwort super, um Super-Methoden aufzurufen (teilweise Fallgrube 9).
Arrow Functions, echte Funktionen ohne eigenes this (teilweise Fallgrube 11).
Sprich: Fallgruben 1, 2, 3 und 10 werden längerfristig erhalten bleiben, die restlichen werden dank Strict Mode und ECMAScript 6 bald verschwinden.
Danksagungen
Dank an Brendan Eich für Feedback zu einer ersten Liste mit Fallgruben und für den Vorschlag, Fallgrube 8 aufzunehmen. Dank an Claude Pache für den Vorschlag, die Umwandlung von Strings zu erwähnen. Dank an Tobias Schneider für diverse Verbesserungsvorschläge.