Kapitel 13. Anweisungen
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 13. Anweisungen

Dieses Kapitel behandelt JavaScripts Anweisungen: Variablendeklarationen, Schleifen, bedingte Anweisungen und andere.

Variablen deklarieren und zuweisen

var wird verwendet, um eine Variable zu deklarieren, was die Variable erstellt und Ihnen ermöglicht, damit zu arbeiten. Das Zuweisungszeichen (=) wird verwendet, um ihr einen Wert zuzuweisen:

var foo;
foo = 'abc';

var erlaubt Ihnen auch, die beiden vorherigen Anweisungen zu einer einzigen zu kombinieren

var foo = 'abc';

Schließlich können Sie auch mehrere var-Anweisungen zu einer zusammenfassen

var x, y=123, z;

Lesen Sie mehr darüber, wie Variablen funktionieren, in Kapitel 16.

Die Körper von Schleifen und bedingten Anweisungen

Zusammengesetzte Anweisungen wie Schleifen und bedingte Anweisungen haben einen oder mehrere eingebettete „Körper“ – zum Beispiel die while-Schleife:

while («condition»)
    «statement»

Für den Körper «statement» haben Sie eine Wahl. Sie können entweder eine einzelne Anweisung verwenden

while (x >= 0) x--;

oder Sie können einen Block verwenden (der als einzelne Anweisung zählt):

while (x > 0) {
    x--;
}

Sie müssen einen Block verwenden, wenn der Körper mehrere Anweisungen umfassen soll. Sofern die gesamte zusammengesetzte Anweisung nicht in einer einzigen Zeile geschrieben werden kann, empfehle ich die Verwendung eines Blocks.

Schleifen

Dieser Abschnitt untersucht die Schleifenanweisungen von JavaScript.

Mechanismen zur Verwendung mit Schleifen

Die folgenden Mechanismen können mit allen Schleifen verwendet werden:

break ⟦«label»⟧
Beendet eine Schleife.
continue ⟦«label»⟧
Stoppt die aktuelle Schleifeniteration und fährt sofort mit der nächsten fort.
Labels

Ein Label ist ein Bezeichner, gefolgt von einem Doppelpunkt. Vor einer Schleife ermöglicht ein Label das Beenden oder Fortsetzen dieser Schleife, selbst aus einer verschachtelten Schleife heraus. Vor einem Block können Sie aus diesem Block ausbrechen. In beiden Fällen wird der Name des Labels zu einem Argument von break oder continue. Hier ist ein Beispiel für das Ausbrechen aus einem Block:

function findEvenNumber(arr) {
    loop: { // label
        for (var i=0; i<arr.length; i++) {
            var elem = arr[i];
            if ((elem % 2) === 0) {
                console.log('Found: ' + elem);
                break loop;
            }
        }
        console.log('No even number found.');
    }
    console.log('DONE');
}

while

Eine while-Schleife:

while («condition»)
    «statement»

führt statement aus, solange condition wahr ist. Wenn condition immer true ist, erhalten Sie eine Endlosschleife

while (true) { ... }

Im folgenden Beispiel entfernen wir alle Elemente eines Arrays und protokollieren sie in der Konsole

var arr = [ 'a', 'b', 'c' ];
while (arr.length > 0) {
    console.log(arr.shift());
}

Hier ist die Ausgabe

a
b
c

do-while

Eine do-while-Schleife:

do «statement»
while («condition»);

führt statement mindestens einmal aus und dann, solange condition wahr ist. Zum Beispiel

var line;
do {
    line = prompt('Enter a number:');
} while (!/^[0-9]+$/.test(line));

for

In einer for-Schleife:

for (⟦«init»⟧; ⟦«condition»⟧; ⟦«post_iteration»⟧)
    «statement»

init wird einmal vor der Schleife ausgeführt, die fortgesetzt wird, solange condition true ist. Sie können var in init verwenden, um Variablen zu deklarieren, aber der Scope dieser Variablen ist immer die gesamte umgebende Funktion. post_iteration wird nach jeder Iteration der Schleife ausgeführt. Wenn man all dies berücksichtigt, ist die vorherige Schleife äquivalent zur folgenden while-Schleife

«init»;
while («condition») {
    «statement»
    «post_iteration»;
}

Das folgende Beispiel ist der traditionelle Weg zur Iteration über Arrays (andere Möglichkeiten werden in Best Practices: Iterating over Arrays) beschrieben

var arr = [ 'a', 'b', 'c' ];
for (var i=0; i<arr.length; i++) {
    console.log(arr[i]);
}

Eine for-Schleife wird endlos, wenn Sie alle Teile des Kopfes weglassen

for (;;) {
    ...
}

for-in

Eine for-in-Schleife:

for («variable» in «object»)
    «statement»

iteriert über alle Eigenschaftsschlüssel von object, einschließlich vererbter. Jedoch werden Eigenschaften, die als nicht aufzählbar markiert sind, ignoriert (siehe Property Attributes and Property Descriptors). Die folgenden Regeln gelten für for-in-Schleifen

  • Sie können var verwenden, um Variablen zu deklarieren, aber der Scope dieser Variablen ist immer die gesamte umgebende Funktion.
  • Eigenschaften können während der Iteration gelöscht werden.

Best Practice: Verwenden Sie for-in nicht für Arrays

Verwenden Sie for-in nicht, umüber Arrays zu iterieren. Erstens iteriert es über Indizes, nicht über Werte:

> var arr = [ 'a', 'b', 'c' ];
> for (var key in arr) { console.log(key); }
0
1
2

Zweitens iteriert es auch über alle (nicht-Index-)Eigenschaftsschlüssel. Das folgende Beispiel verdeutlicht, was passiert, wenn Sie eine Eigenschaft foo zu einem Array hinzufügen

> var arr = [ 'a', 'b', 'c' ];
> arr.foo = true;
> for (var key in arr) { console.log(key); }
0
1
2
foo

Daher sind Sie mit einer normalen for-Schleife oder der Array-Methode forEach() besser bedient (siehe Best Practices: Iterating over Arrays).

Best Practice: Seien Sie vorsichtig mit for-in für Objekte

Die for-in-Schleife iteriert über alle (aufzählbaren) Eigenschaften, einschließlich vererbter. Das ist möglicherweise nicht das, was Sie wollen. Verwenden wir den folgenden Konstruktor, um das Problem zu veranschaulichen:

function Person(name) {
    this.name = name;
}
Person.prototype.describe = function () {
    return 'Name: '+this.name;
};

Instanzen von Person erben die Eigenschaft describe von Person.prototype, was von for-in gesehen wird

var person = new Person('Jane');
for (var key in person) {
    console.log(key);
}

Hier ist die Ausgabe

name
describe

Normalerweise ist der beste Weg, for-in zu verwenden, vererbte Eigenschaften über hasOwnProperty() zu überspringen

for (var key in person) {
    if (person.hasOwnProperty(key)) {
        console.log(key);
    }
}

Und hier ist die Ausgabe

name

Es gibt noch einen letzten Vorbehalt: person kann eine Eigenschaft hasOwnProperty haben, die die Überprüfung verhindern würde. Um sicherzugehen, müssen Sie direkt auf die generische Methode (siehe Generic Methods: Borrowing Methods from Prototypes) Object.prototype.hasOwnProperty verweisen

for (var key in person) {
    if (Object.prototype.hasOwnProperty.call(person, key)) {
        console.log(key);
    }
}

Es gibt andere, bequemere Mittel zur Iteration über Eigenschaftsschlüssel, die in Best Practices: Iterating over Own Properties beschrieben werden.

for each-in

Diese Schleife existiert nur in Firefox. Verwenden Sie sie nicht.

Bedingte Anweisungen

Dieser Abschnitt behandelt die bedingten Anweisungen von JavaScript.

if-then-else

In einer if-then-else-Anweisung:

if («condition»)
    «then_branch»
else
    «else_branch»⟧

then_branch und else_branch können entweder einzelne Anweisungen oder Blöcke von Anweisungen sein (siehe The Bodies of Loops and Conditionals).

Ketten von if-Anweisungen

Sie können mehrere if-Anweisungen verketten:

if (s1 > s2) {
    return 1;
} else if (s1 < s2) {
    return -1;
} else {
    return 0;
}

Beachten Sie, dass in der vorherigen Anweisung alle else-Zweige einzelne Anweisungen (if-Anweisungen) sind. Programmiersprachen, die nur Blöcke für else-Zweige zulassen, benötigen eine Art else-if-Zweig zum Verketten.

Fallstrick: hängendes else

Der else-Zweigder folgenden Anweisung wird als hängend bezeichnet, da nicht klar ist, zu welcher der beiden if-Anweisungen er gehört:

if («cond1») if («cond2») «stmt1» else «stmt2»

Hier ist eine einfache Regel: verwenden Sie Klammern. Der vorherige Ausschnitt ist äquivalent zum folgenden Code (bei dem offensichtlich ist, zu wem das else gehört):

if («cond1») {
    if («cond2») {
        «stmt1»
    } else {
        «stmt2»
    }
}

switch

Eine switch-Anweisung:

switch («expression») {
    case «label1_1»:
    case «label1_2»:
        ...
        «statements1»
        break;
    case «label2_1»:
    case «label2_2»:
        ...
        «statements2»
        break;
    ...
    default:
        «statements_default»
        break;⟧⟧
}

wertet expression aus und springt dann zur case-Klausel, deren Label dem Ergebnis entspricht. Wenn kein Label übereinstimmt, springt switch zur default-Klausel, falls vorhanden, oder tut nichts sonst.

Der „Operand“ nach case kann jeder Ausdruck sein; er wird über === mit dem Parameter von switch verglichen.

Wenn Sie eine Klausel nicht mit einer beendenden Anweisung abschließen, wird die Ausführung in die nächste Klausel fortgesetzt. Die am häufigsten verwendete beendende Anweisung ist break. Aber return und throw funktionieren ebenfalls, auch wenn sie normalerweise mehr als nur die switch-Anweisung verlassen.

Das folgende Beispiel verdeutlicht, dass Sie break nicht verwenden müssen, wenn Sie throw oder return verwenden

function divide(dividend, divisor) {
    switch (divisor) {
        case 0:
            throw 'Division by zero';
        default:
            return dividend / divisor;
    }
}

In diesem Beispiel gibt es keine default-Klausel. Daher geschieht nichts, wenn fruit keinem der case-Labels entspricht

function useFruit(fruit) {
    switch (fruit) {
        case 'apple':
            makeCider();
            break;
        case 'grape':
            makeWine();
            break;
        // neither apple nor grape: do nothing
    }
}

Hier gibt es mehrere case-Labels hintereinander

function categorizeColor(color) {
    var result;
    switch (color) {
        case 'red':
        case 'yellow':
        case 'blue':
            result = 'Primary color: '+color;
            break;
        case 'orange':
        case 'green':
        case 'violet':
            result = 'Secondary color: '+color;
            break;
        case 'black':
        case 'white':
            result = 'Not a color';
            break;
        default:
            throw 'Illegal argument: '+color;
    }
    console.log(result);
}

Dieses Beispiel zeigt, dass der Wert nach case ein beliebiger Ausdruck sein kann

function compare(x, y) {
    switch (true) {
        case x < y:
            return -1;
        case x === y:
            return 0;
        default:
            return 1;
    }
}

Die vorherige switch-Anweisung sucht nach einer Übereinstimmung für ihren Parameter true, indem sie die case-Klauseln durchläuft. Wenn einer der case-Ausdrücke zu true ausgewertet wird, wird der entsprechende case-Körper ausgeführt. Daher ist der vorherige Code äquivalent zur folgenden if-Anweisung

function compare(x, y) {
    if (x < y) {
        return -1;
    } else if (x === y) {
        return 0;
    } else {
        return 1;
    }
}

Normalerweise sollten Sie die letztere Lösung bevorzugen; sie ist selbsterklärender.

Die with-Anweisung

Dieser Abschnitt erklärt, wie die with-Anweisung in JavaScript funktioniert und warum ihre Verwendung abgeraten wird.

Syntax und Semantik

Die Syntax der with-Anweisung ist wie folgt

with («object»)
    «statement»

Sie verwandelt die Eigenschaften von object in lokale Variablen für statement. Zum Beispiel

var obj = { first: 'John' };
with (obj) {
    console.log('Hello '+first); // Hello John
}

Ihr beabsichtigter Verwendungszweck ist es, Redundanz zu vermeiden, wenn mehrmals auf ein Objekt zugegriffen wird. Das Folgende ist ein Beispiel für Code mit Redundanzen

foo.bar.baz.bla   = 123;
foo.bar.baz.yadda = 'abc';

with macht dies kürzer

with (foo.bar.baz) {
    bla   = 123;
    yadda = 'abc';
}

Die with-Anweisung ist veraltet

Die Verwendung der with-Anweisung wird generell abgeraten (der nächste Abschnitt erklärt warum). Sie ist zum Beispiel im Strict Mode verboten:

> function foo() { 'use strict'; with ({}); }
SyntaxError: strict mode code may not contain 'with' statements

Techniken zur Vermeidung der with-Anweisung

Vermeiden Sie Code wie diesen:

// Don't do this:
with (foo.bar.baz) {
    console.log('Hello '+first+' '+last);
}

Verwenden Sie stattdessen eine temporäre Variable mit einem kurzen Namen

var b = foo.bar.baz;
console.log('Hello '+b.first+' '+b.last);

Wenn Sie die temporäre Variable b nicht dem aktuellen Scope aussetzen möchten, können Sie eine IIFE (siehe Introducing a New Scope via an IIFE) verwenden

(function () {
    var b = foo.bar.baz;
    console.log('Hello '+b.first+' '+b.last);
}());

Sie haben auch die Möglichkeit, das Objekt, auf das Sie zugreifen möchten, zu einem Parameter der IIFE zu machen

(function (b) {
    console.log('Hello '+b.first+' '+b.last);
}(foo.bar.baz));

Die Begründung für die Veralterung

Um zu verstehen, warum with veraltet ist, schauen Sie sich das folgende Beispiel an und beachten Sie, wie das Argument der Funktion die Funktionsweise komplett verändert:

function logMessage(msg, opts) {
    with (opts) {
        console.log('msg: '+msg); // (1)
    }
}

Wenn opts eine Eigenschaft msg hat, dann greift die Anweisung in Zeile (1) nicht mehr auf den Parameter msg zu. Sie greift auf die Eigenschaft zu

> logMessage('hello', {})  // parameter msg
msg: hello
> logMessage('hello', { msg: 'world' })  // property opts.msg
msg: world

Es gibt drei Probleme, die die with-Anweisung verursacht

Performance leidet
Die Variablensuche wird langsamer, da ein Objekt vorübergehend in die Scope-Kette eingefügt wird.
Code wird unvorhersehbarer

Sie können nicht feststellen, worauf sich ein Bezeichner bezieht, indem Sie seine syntaktischen Umgebung (seinen lexikalischen Kontext) betrachten. Laut Brendan Eich war dies der eigentliche Grund, warum with veraltet wurde, und nicht Leistungserwägungen

with verletzt den lexikalischen Scope und macht die Programmanalyse (z. B. für Sicherheit) schwierig bis unmöglich.

Minifier (beschrieben in Kapitel 32) können Variablennamen nicht verkürzen
Innerhalb einer with-Anweisung können Sie nicht statisch bestimmen, ob ein Name sich auf eine Variable oder eine Eigenschaft bezieht. Nur Variablen können von Minifiern umbenannt werden.

Hier ist ein Beispiel dafür, wie with Code anfällig macht

function foo(someArray) {
    var values = ...;  // (1)
    with (someArray) {
        values.someMethod(...);  // (2)
        ...
    }
}
foo(myData);  // (3)

Sie können den Funktionsaufruf in Zeile (3) verhindern, selbst wenn Sie keinen Zugriff auf das Array myData haben.

Wie? Indem Sie eine Eigenschaft values zu Array.prototype hinzufügen. Zum Beispiel

Array.prototype.values = function () {
    ...
};

Jetzt ruft der Code in Zeile (2) someArray.values.someMethod() anstelle von values.someMethod() auf. Der Grund ist, dass values innerhalb der with-Anweisung jetzt someArray.values und nicht mehr die lokale Variable aus Zeile (1) bezeichnet.

Dies ist kein reines Gedankenexperiment: die Array-Methode values() wurde zu Firefox hinzugefügt und brach das TYPO3-Content-Management-System. Brandon Benvie fand heraus, was schiefgelaufen war.

Die debugger-Anweisung

Die Syntax für die debugger-Anweisung ist wie folgt:

debugger;

Wenn ein Debugger aktiv ist, fungiert diese Anweisung als Breakpoint; wenn nicht, hat sie keine beobachtbare Auswirkung.

Weiter: 14. Exception Handling