JavaScript/Kontrollstruktur
Man unterscheidet grundsätzlich zwei Arten von Kontrollstrukturen: Verzweigungen und Schleifen. Mittels Verzweigungen ist es möglich, die Ausführung einer oder mehrerer Anweisungs-Blöcke von Bedingungen abhängig zu machen (Fallunterscheidung). Schleifen ermöglichen, einen Anweisungs-Block wiederholt ausführen zu lassen.
Inhaltsverzeichnis
Wenn-Dann-Bedingungen mit "if"
Man kann die Ausführung von Anweisungen von Bedingungen abhängig machen. In einer Verzweigung (bedingte Anweisung) wird ein Anweisungsblock nur durchlaufen, wenn eine Bedingung erfüllt ist. Alternativ kann beim Nichterfüllen der Bedingung ein zweiter Anweisungsblock durchlaufen werden.[1][2]
if (Ausdruck) {
//Anweisungsblock des True-Zweigs (wird ausgeführt, wenn Bedingung erfüllt ist)
Anweisung;
Anweisung;
} else {
//Anweisungsblock des False-Zweigs (wird ausgeführt, wenn Bedingung nicht erfüllt ist)
Anweisung;
Anweisung;
}
Mit if
leitest du eine Wenn-Dann-Bedingung ein (if = wenn). Dahinter folgt, in Klammern stehend, die Formulierung der Bedingung. Um solche Bedingungen zu formulieren, braucht man Vergleichsoperatoren und in den meisten Fällen auch Variablen. Für Fälle, in denen die Bedingung nicht erfüllt ist, kannst du einen "andernfalls"-Zweig definieren. Dies geschieht durch else
(else = sonst). Der Else-Zweig ist nicht zwingend erforderlich.
if
oder else
folgen, diese immer in geschweifte Klammern eingeschlossen werden müssen (siehe auch den Abschnitt über Anweisungsblöcke).
document.addEventListener('DOMContentLoaded', Geheim);
function Geheim () {
let Passwort = 'Traumtaenzer';
let Eingabe = window.prompt('Bitte geben Sie das Passwort ein', '');
if (Eingabe != Passwort) {
alert('Falsches Passwort!');
} else {
location.href = 'geheim.htm';
}
}
Das Beispiel stellt eine einfache Passwortabfrage dar.
Das Script ist in einer Funktion namens Geheim()
notiert, die aufgerufen wird, sobald die HTML-Datei vollständig geladen ist. Dazu wird dynamisch mit addEventListener das DOMContentLoaded-Event überwacht.
Innerhalb der Funktion fordert ein Dialogfenster (window.prompt()
) den Anwender auf, das Passwort einzugeben. Der Rückgabewert von window.prompt()
, das eingegebene Passwort, wird in der Variablen Eingabe
gespeichert.
Mit if (Eingabe != Passwort
) fragt das Script ab, ob die Eingabe anders lautet als der der Variablen Passwort
zuvor zugewiesene Wert Traumtaenzer
. Ist dies der Fall, sind also beide Werte nicht gleich, dann war die Eingabe falsch. In diesem Fall wird mit alert()
eine entsprechende Meldung ausgegeben. Im anderen Fall (else
), wenn Eingabe
und Passwort
den gleichen Wert haben, wird mit location.href eine andere Seite aufgerufen, nämlich die "geschützte" Seite.
if-Kette
An Stelle einer einfachen Entweder-Oder Entscheidung kann es auch vorkommen, dass du zwischen mehr als zwei Fällen unterscheiden musst. Um das zu lösen, kann man im else
-Zweig der if
-Anweisung eine weitere if
-Anweisung programmieren, und in deren else
-Zweig noch eine, und so weiter, ganz nach Bedarf. Manche Programmiersprachen besitzen für diesen Zweck eine eigene elseif
Klausel - nicht so JavaScript. Es ist auch nicht erforderlich. Schreibe das if
einfach hinter das else
, mit einer Leerstelle dazwischen, und ändere die Einrücktiefe nicht. Dann sieht es wie ein elseif
aus.
let Eingabe = window.prompt('Geben Sie eine Zahl zwischen 50 und 100 ein:', ''),
Eingabewert = parseInt(Eingabe),
Text;
if (Eingabewert < 50) {
Text = 'Die Zahl ' + Eingabe + ' ist sicher zu klein.';
}
else if (Eingabewert > 100) {
Text = 'Die Zahl ' + Eingabe + ' ist eindeutig zu groß.';
}
else
Text = 'Na bitte, Sie haben die Aufgabe verstanden!';
}
alert(Text);
Es gibt auch die Möglichkeit, eine if-Kette durch den switch-Befehl zu ersetzen. Dazu später mehr.
Häufiges Problem - Zuweisung statt Vergleich
Ein Problem der Programmiersprachen, die auf die Programmiersprache C zurückgehen, ist die Ähnlichkeit von Vergleich und Zuweisung. Die Formulierungen if (a == 2)
und if (a = 2)
hat man schnell verwechselt. JavaScript bemerkt diesen Fehler nicht, denn es ist gültige Syntax. Die Wertzuweisung an eine Variable wird als Ausdruck aufgefasst, der einen Wert hat - nämlich den Wert, den man zuweist. Verschärft wird das Problem durch die Tatsache, dass JavaScript kein strenges Typsystem besitzt, das Alarm schlagen würde, wenn man der if
-Anweisung einen Zahlenwert präsentiert, wobei sie doch einen boolescher Wert erwartet. Statt dessen gibt es Regelwerk, mit dem jeder mögliche Wert auf die booleschen Werte true oder false abgebildet werden kann. Du kannst das im Abschnitt „Was ist Wahrheit?“ im Artikel zum Boolean-Typ nachlesen.
Die Regel für Zahlen lautet: 0 entspricht false
, alles andere entspricht true
. Der Wert der Zuweisung a = 2
ist 2, und 2 ist truthy, weswegen im folgenden Beispiel immer gemeldet wird, dass a gleich 2 sei.
if (a = 2)
alert('a ist 2');
else
alert('a ist nicht 2');
Eine einfache Möglichkeit, sich gegen solche Fehler zu schützen, besteht darin, die Abfrage umzudrehen. Schreibe 2 == a
statt a == 2
. Zuweisungen an eine Zahl sind ein Syntaxfehler und brechen das Programm ab. Wenn du bei dieser Schreibweise ein Gleichheitszeichen vergisst, fällt es sofort auf.
Das hilft dir natürlich nicht, wenn du zwei Variableninhalte miteinander vergleichen musst. Aber vielleicht tröstet dich, dass diese Sache wie Skateboardfahren ist: man muss etliche Male auf die Nase gefallen sein, bis man es heraus hat.
Einfache Entweder-Oder-Abfrage
Für einfache Entweder-Oder-Bedingungen gibt es mit dem bedingten (ternären) Operator eine spezielle Syntax, die man alternativ zur if/else-Anweisung verwenden kann.
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('#check').addEventListener('click', Antwort);
function Antwort () {
let Ergebnis = (document.Formular.Eingabe.value == '42') ? 'RICHTIG!' : 'FALSCH!';
let text = 'Die Antwort ist ' + Ergebnis;
document.querySelector('output').innerText = text;
}
});
Das Beispiel enthält eine JavaScript Funktion namens Antwort()
.
Wenn das Dokument geladen wird, wird mit addEventListener ein EventHandler hinzugefügt, der aufgerufen wird, wenn der Anwender in dem Formular auf den Klick-Button mit der Aufschrift OK
und der id="check" klickt, und zwar mit dem click-Event.
Die Funktion prüft, ob der Wert des Eingabefeldes im Formular (document.Formular.Eingabe.value
) den Wert 42
hat. Abhängig davon wird der Variablen Ergebnis
entweder der Wert RICHTIG!
oder FALSCH!
zugewiesen. Anschließend wird ein entsprechender Satz zusammengesetzt (siehe dazu auch Operator für Zeichenkettenverknüpfung) und im output-Element ausgegeben.
Eine einfache Entweder-Oder-Abfrage wird mit einer Bedingung eingeleitet. Die Bedingung muss in Klammern stehen, im Beispiel (document.Formular.Eingabe.value == "42"
). Dahinter wird ein Fragezeichen notiert. Hinter dem Fragezeichen wird ein Wert angegeben, der aktuell ist, wenn die Bedingung erfüllt ist. Dahinter folgt ein Doppelpunkt, und dahinter ein Wert für den Fall, dass die Bedingung nicht erfüllt ist. Da es sich um Werte handelt, die für die Weiterverarbeitung nur sinnvoll sind, wenn sie in einer Variablen gespeichert werden, wird einer solchen Entweder-Oder-Abfrage in der Regel eine Variable vorangestellt, im Beispiel die Variable Ergebnis
. Der Variablen wird durch diese Art von Anweisung das Ergebnis der Entweder-Oder-Abfrage zugewiesen.
Um Bedingungen zu formulieren, braucht man Vergleichsoperatoren.
Fallunterscheidung mit "switch"
Mit if
und else
kann man genau zwei Fälle unterscheiden. Wenn du feiner differenzieren, also zwischen mehreren Fällen unterscheiden willst, kannst du zwar eine if-Kette aufbauen, aber es gibt noch eine elegantere Möglichkeit: die Fallunterscheidung mit "switch".[3][4]
let Eingabe = window.prompt('Geben Sie eine Zahl zwischen 1 und 4 ein:', ''),
Text = '';
switch (Eingabe) {
case "1":
Text = 'Sie sind sehr bescheiden!';
break;
case "2":
Text = 'Sie sind ein aufrichtiger Zweibeiner!';
break;
case "3":
Text = 'Sie haben ein Dreirad gewonnen!';
break;
case "4":
Text = 'Gehen Sie auf allen Vieren und werden Sie bescheidener!';
break;
default:
Text = 'Sie bleiben leider dumm!';
break;
}
let ausgabe = document.getElementById('ausgabe');
ausgabe.textContent = Text;
Mit switch
leitet man eine Fallunterscheidung ein (switch = Schalter). Die Anweisung funktioniert so, dass man einen zu prüfenden Wert vorgibt, und dann eine Liste von möglichen Vergleichswerten mit den zugehörigen Aktionen folgt.
Den zu prüfenden Wert notiert man, in runde Klammern eingeschlossen, direkt hinter der switch
-Anweisung. Dieser Wert kann aus einer Variablen kommen oder ein Ausdruck sein. Es kann auch eine Konstante sein, auch wenn das vielleicht zunächst sinnlos erscheinen mag.
Im Beispiel wird die Variable mit dem Namen Eingabe
verwendet. Diese Variable wird vor der Fallunterscheidung mit einem Wert versorgt, denn ein Dialogfenster (window.prompt()
) mit einem Eingabefeld fordert den Anwender auf, eine Zahl zwischen 1 und 4 einzugeben. Der eingegebene Wert wird in Eingabe
gespeichert.
Die eigentliche Liste von Vergleichen wird in geschweifte Klammern eingeschlossen. Jeder einzelne Vergleich beginnt mit dem Schlüsselwort case
(case = Fall), gefolgt von dem Wert, der mit dem zu prüfenden Wert zu vergleichen ist. Für den Vergleichswert sind keine Klammern erforderlich, den Übergang vom Vergleichswert zu den Anweisungen, die bei Gleichheit auszuführen sind, markiert man durch einen Doppelpunkt. Die Klausel case "1":
im obigen Beispiel bedeutet also so viel wie: 'wenn der zu prüfende Wert der Zeichenkette "1" entspricht'.
==
und ===
. Das doppelte Gleichheitszeichen versucht, den Typen der abgefragten Werte aneinander anzupassen, bevor es den Vergleich durchführt, deswegen ist beispielsweise "2" == 2
wahr. Das dreifache Gleichheitszeichen ist strenger; wenn die Typen der verglichenen Werte verschieden sind, ist das Vergleichsergebnis grundsätzlich "falsch". Die switch-Anweisung verwendet den ===
‑Vergleich.Die Anweisungen für einen Fall werden durch die Anweisung break
(break = abbrechen) abgeschlossen. Wenn man diese Anweisung nicht notiert, läuft die Programmausführung einfach in den nächsten case
hinein. Im Normalfall wirst du das nicht wollen - aber man kann das auch ausnutzen. Dazu gleich mehr.
Für den Fall, dass keiner der definierten Fälle zutrifft, kann am Ende der Fallunterscheidung der Fall default:
notiert werden (ohne einen Wert). Die darunter stehenden Anweisungen werden ausgeführt, wenn keiner der anderen Fälle zutrifft.
Besonderheit: gleiche Anweisungen
Manchmal soll für verschiedene Werte der Fallunterscheidung derselbe Code ausgeführt werden. Dazu setze einfach die entsprechenden Fälle untereinander und nutze den Umstand, dass ein fehlendes break
in den nächsten case
hineinläuft (oder „durchfällt“ - der englische Begriff hierfür ist fall-through).
let Eingabe = window.prompt('Geben Sie eine Zahl von 1 bis 6 ein:', ''),
Text = 'Ihre Eingabe ist ';
switch (Eingabe) {
case "1":
Text += 'weder eine Prim- noch eine zusammengesetzte Zahl.';
break;
case "2":
case "3":
case "5":
Text += 'eine Primzahl.';
break;
case "4":
case "6":
Text += 'eine zusammengesetzte Zahl.';
break;
default:
Text += 'ungültig.';
break;
}
Besonderheit: Wertebereiche
Eingangs wurde erwähnt, dass man hinter switch
auch eine Konstante notieren kann. Ebenso lässt sich hinter case
an Stelle einer Konstanten auch ein Ausdruck schreiben. Das dient dazu, die switch-Anweisung sozusagen auf den Kopf zu stellen. Statt einen berechneten Wert mit einer Liste von Konstanten zu vergleichen, kannst du auch eine Konstante mit einer Liste von berechneten Werten vergleichen.
Im einfachsten Fall schreibe in die switch
-Anweisung den Prüfwert true
und verwende in den case
-Klauseln Ausdrücke, die einen Wahrheitswert liefern. JavaScript geht die case-Klauseln von oben nach unten durch und wertet die Ausdrücke soweit aus, bis es eine Übereinstimmung mit dem Prüfwert findet. Die weiteren case
-Klauseln bleiben unbeachtet.
let Eingabe = window.prompt('Geben Sie eine Zahl zwischen 50 und 100 ein:', ''),
EingabeWert = parseInt(Eingabe),
Text;
switch (true) {
case EingabeWert < 50:
Text = 'Die Zahl ' + Eingabe + ' ist sicher zu klein.';
break;
case EingabeWert > 100:
Text = 'Die Zahl ' + Eingabe + ' ist eindeutig zu groß.';
break;
default:
Text = 'Na bitte, Sie haben die Aufgabe verstanden!';
break;
}
Man kann die Ausdrücke hinter case
für bessere Lesbarkeit in Klammern setzen - syntaktisch notwendig ist das nicht. Und man ist nicht auf einen switch (true)
beschränkt, da kann jeder Wert stehen. Interessant könnte ein switch (false)
sein, wenn du eine Liste von Prüfungen durchlaufen möchtest und einen Fehler erzeugen willst, sobald eine dieser Prüfungen nicht erfüllt ist. Oder ein switch (5)
, wenn du mehrere Variablen mit Werten hast und eine finden willst, deren Wert 5 ist. Es hängt ganz von deiner Problemstellung ab.
Schleifen
Schleifen sind ein Programmierkonstrukt und erfüllen den Zweck, eine Anweisung oder eine Gruppe von Anweisungen solange auszuführen, bis eine bestimmte Bedingung erfüllt ist, oder nicht mehr erfüllt ist. Die zu wiederholenden Anweisungen nennt man den Schleifenrumpf und die steuernde Bedingung demnach die Schleifenbedingung.
Eigentlich benötigt man in einer Programmiersprache gar keine eigenen Befehle, um Schleifen zu bilden. In der schlechten alten Zeit der unstrukturierten Programmierung verwendete man den goto Befehl, um das Programm an einer anderen Stelle fortzusetzen. Zusammen mit einer bedingten Anweisung genügte das, um im Programmablauf ein Stück zurück zu springen und so zu wiederholen. Das Problem dabei ist: das einzige, was einen Programmierer daran hindert, in seinem Programm kreuz und quer zu springen, so dass der Programmablauf nachher wie ein Topf Spaghetti aussieht, ist Disziplin und die Angst vor dem Schiffbruch, den man mit dieser Art der Programmsteuerung unweigerlich irgendwann erleidet.
Bekannte Informatiklehrer wie Edsger Dijkstra und Niklaus Wirth empfahlen deshalb, dass die Auswertung der Schleifenbedingung entweder vor der Ausführung des Anweisungsblocks erfolgen sollte - die kopfgesteuerte Schleife - oder nach dessen Ausführung - die fußgesteuerte Schleife. JavaScript bietet für diese beiden Schleifentypen eigene Syntax an: die while-Schleife für die Kopfsteuerung und die do...while Schleife für die Fußsteuerung.
Schleifen müssen oftmals in einer bestimmten Anzahl durchlaufen werden. Ebenso setzt man sie ein, um bestimmte Datenstrukturen wie Arrays oder Listen von Anfang bis zum Ende zu durchlaufen. Mit der for-Schleife, der for..in-Schleife und der for..of-Schleife bietet JavaScript hier zusätzlichen Komfort.
Und schließlich hat sich herausgestellt, dass es recht umständlichen Code ergeben kann, wenn man eine Schleifenbedingung wirklich nur vor oder nach dem Anweisungsblock notiert. Es passiert oft, dass man mitten im Schleifenrumpf feststellt, dass der Rest des Rumpfes nicht mehr durchlaufen werden muss und der nächste Durchlauf beginnen kann. Und es gibt viele Fälle, wo man feststellt, dass die Schleife abgebrochen werden kann. Man kann das gemäß der reinen Lehre mit bedingten Anweisungen und einer reinen kopf- oder fußgesteuerten Schleife lösen, was aber zu tief geschachtelten if-Abfragen führt und schwer lesbar ist. Auf diese Weise hat das goto doch noch seinen Weg nach JavaScript gefunden, in Form der Anweisungen continue
und break
.
Kopfgesteuerte Schleifen mit "while"
Die while-Schleife wertet zunächst die Schleifenbedingung aus. Ergibt sich dabei der Wert true
(oder ein true-artiger Wert), wird der Schleifenrumpf ausgeführt. Danach beginnt der Ablauf von vorn.
Dieser Schleifentyp eignet sich
- wenn du im Voraus nicht weißt, wie oft die Schleife zu durchlaufen ist
- wenn es beabsichtigt ist, dass der Schleifenrumpf eventuell gar nicht durchlaufen wird
Syntax:
while ( Bedingung ) { Anweisungen }
Eine while-Schleife beginnt mit dem Schlüsselwort while
(while = solange). Dahinter folgt, in runden Klammern ( und ) stehend, die Schleifenbedingung. Um eine Bedingung zu formulieren, braucht man beispielsweise Vergleichsoperatoren oder logische Operatoren. Danach folgt der Schleifenrumpf, der eine einzelne JavaScript-Anweisung sein kann oder ein Block aus mehreren Anweisungen, die in geschweifte Klammern { und } gesetzt werden.
Schauen wir uns als Beispiel ein Stück Code an, das Leerstellen aus einer Eingabe entfernt. JavaScript bietet dazu zwar die Methode trim an, aber stellen wir uns vor, wir müssten den Internet Explorer 9 verwenden. Der kannte die noch nicht.
let eingabe = document.getElementById("vorname").value;
let startPos = 0,
endPos = eingabe.length - 1,
entfernt, restlaenge, ergebnis;
while (startPos <= endPos && eingabe[startPos] == ' ') {
startPos++;
}
if (endPos > startPos) {
while (eingabe[endPos] == ' ') {
endPos--;
}
}
restlaenge = endPos+1 - startPos;
entfernt = eingabe.length - restlaenge;
ergebnis = eingabe.substr(startPos, restlaenge);
// Setze den Antwortsatz zusammen
let antwort = "Ich habe aus '" + eingabe + "' " + entfernt +
" unnötige Leerstellen entfernt, '" + ergebnis + "'";
// Schreibe die Antwort in das p Element mit der id="ergebnis"
document.getElementById("ergebnis").textContent = antwort;
Das Beispiel gehört zu einer HTML-Seite, auf der sich ein Eingabeelement mit der id "vorname" und ein Ausgabeelement mit der id "ergebnis" befinden, sowie einen Button, der den Programmcode aufruft. Klicke auf "So sieht's aus" oder "ausprobieren", um sich das vollständige Beispiel anzuschauen.
Zweck des Beispiels ist, unnötigen Leerraum aus der Eingabe des Anwenders. Dazu prüft es mit Hilfe von zwei while
-Schleifen, ob am Anfang oder am Ende der Eingabe Leerstellen zu finden sind. Dazu setzt es die Variablen startPos
und endPos
auf das erste und letzte Zeichen der Eingabe (Positionen in einer Zeichenkette beginnen in JavaScript mit 0 und nicht mit 1).
Die Bedingung der ersten while
-Schleife besteht aus zwei Teilen, die durch &&
, also UND, verknüpft sind. Teil 1 stellt sicher, dass die Schleife endet und nicht über das Ende der Eingabe hinaus läuft. endPos
zeigt auf das letzte Zeichen der Eingabe, solange also startPos
kleiner oder gleich endPos
ist, ist alles gut. Teil 2 prüft, ob an der Position startPos
eine Leerstelle steht. Ist eine von beiden Teilbedingungen falsch, ist das Ergebnis der UND-Verknüpfung falsch und der Schleifenrumpf wird nicht mehr ausgeführt. Hat der Anwender seinen Namen ohne Leerstelle am Anfang eingegeben, ist das sofort er Fall und startPos
bleibt auf 0.
Die zweite while
-Schleife funktioniert ähnlich, nur wird jetzt endPos > startPos
vorab geprüft. Ist diese Bedingung falsch, so ist bereits die erste Schleife bis zum letzten Zeichen der Eingabe gelaufen und wir müssen nichts weiter tun. Andernfalls ist startPos
kleiner als endPos
und zeigt auf ein Zeichen, dass keine Leerstelle ist, so dass sichergestellt ist, dass die zweite Schleife ein Ende findet. Sie läuft so lange, wie endPos
auf eine Leerstelle zeigt und setzt endPos
solange zurück, bis das nicht mehr der Fall ist.
endPos > startPos
in die Schleifenbedingung aufgenehmen.Nach den beiden Schleifen wird berechnet, wieviele Zeichen übrig bleiben und wieviele Leerstellen entfernt werdeb. Mit Hilfe der substr
-Methode, über die Zeichenketten verfügen, werden diese Zeichen ab startPos
aus der Eingabe herausgeholt und der Eingabewert damit überschrieben. Mit der alert-Funktion wird dem Anwender das Ergebnis mitgeteilt.
Anmerkung: Das Beispiel vereinfacht die Realität. Der Zeichensatz Ihres Computers verfügt über etliche Zeichen, die als Leerraum dargestellt werden und die hier eigentlich alle berücksichtigt werden müssten.
Fußgesteuerte Schleifen mit "do-while"
Die do-while-Schleife verhält sich ähnlich wie die while-Schleife, wertet aber die Schleifenbedingung erst nach Ausführung des Schleifenrumpfes aus. Dieser Schleifentyp eignet sich dann, wenn man mindestens einmal durch den Schleifenrumpf durchlaufen müssen, um entscheiden zu können, ob der Rumpf wiederholt werden muss oder nicht.
Syntax:
do { Anweisungen } while ( Bedingung );
Eine do-while-Schleife beginnt mit dem Schlüsselwort do
(do = tue etwas). Hinter dem do
folgt eine einzelne Anweisung oder ein Anweisungsblock in geschweiften Klammern { und }. Darauf folgt als nächstes das Schlüsselwort while
, hinter dem in runden Klammern ( und ) die Schleifenbedingung steht.
Der Aufbau der do-while-Schleife ist eigentlich seltsam. Zum einen ist das do
technisch gar nicht nötig, es ist eher eine Lesehilfe, damit man nicht unvermittelt vor dem Beginn eines Anweisungsblocks steht. Andererseits, wenn man es schon einmal hat, könnte man auch ganz auf die geschweiften Klammern verzichten. Das geht aber wie in jeder anderen Kontrollstruktur nur, wenn der Schleifenrumpf nur aus einer einzelnen Anweisung besteht. JavaScript orientiert sich hier schlicht an seinem Vorbild, der Programmiersprache C.
Als Beispiel für eine do-while Schleife schauen wir uns ein Script an, dass die Prioritäten der Anwenderin überprüft. Es bittet sie, die Abkürzung HTML zu erklären. Die Schleife endet, wenn sie richtig antwortet oder zum dritten Mal zeigt, dass sie den Sinn des Lebens nicht versteht.
const richtigeAntwort = "how to make love";
let eingabe = "",
nachricht = "",
zaehler = 0;
do {
zaehler++;
eingabe = prompt(zaehler + ". Versuch: Was bedeutet 'HTML'?", "");
} while (eingabe != richtigeAntwort && zaehler < 3);
if (eingabe != richtigeAntwort) {
nachricht = "Lernen Sie erst mal HTML! ...";
}
else {
nachricht = "Fein, Sie haben verstanden worum es geht...";
}
document.querySelector('output').innerText = nachricht;
Die do-while Schleife eignet sich für diese Situation, weil ohne eine Anwendereingabe nichts überprüft werden kann. Innerhalb der Schleife wird die Eingabe über ein prompt
-Fenster erfasst und gezählt, wie oft schon etwas eingegeben wurde.
Die Schleifenbedingung bestehz aus zwei Teilbedingungen, die durch den UND-Operator &&
verknüpft sind. Die Schleifenbedingung muss true
liefern, damit die Schleife wiederholt wird, deshalb ist die Teilbedingung 1, die Prüfung auf die richtige Eingabe, mit dem Ungleich-Operator !=
formuliert. Die Teilbedingung 2 prüft, ob die Anwenderin noch vor dem dritten Versuch ist. Nach dem dritten Durchlauf ist diese Teilbedingung falsch und die Schleife endet auf jeden Fall.
Wenn die Anwenderin die richtige Antwort im dritten Durchlauf eingibt, steht also nicht fest, aus welchen der beiden möglichen Ursachen die Schleife beendet wurde. Um das zu entscheiden, wird deshalb anschließend mit Hilfe einer if
-Abfrage nochmals überprüft, ob die Eingabe falsch war, und je nachdem eine passende Antwortnachricht in der Variablen nachricht
eingetragen. Diese wird dann mit document.querySelector('output').innerText
in ein output-Element der Webseite eingefügt.
Es lohnt, an diesem Beispiel über eine verständliche Zählersteuerung nachzudenken. Der Zählerstand wird der Anwenderin angezeigt, muss also in den prompt-Fenstern von 1 bis 3 zählen. Und es ist für die Lesbarkeit des Programms sinnvoll, in der Schleifenbedingung zaehler < 3
abzufragen, bei einem Stand von 3 also abzubrechen. Das lässt sich dadurch erreichen, dass man die Variable auf 0 initialisiert und in der Schleife zunächst hochzählt. Man könnte statt dessen auch den Zähler vor der Schleife auf 1 initialisieren und ihn erst nach dem prompt, am Ende der Schleife, um 1 erhöhen. Aber dann müsste in der Schleifenbedingung zaehler <= 3
programmiert werden, was man so lesen könnte, dass nach dem dritten Durchlauf ein weiterer folgen soll. Deswegen wäre das die schlechtere Wahl.
Weitere Möglichkeiten, um Schleifen abzubrechen, werden weiter unten beschrieben.
Das nächste Beispiel soll noch einmal den Unterschied zwischen while und do-while verdeutlichen. Bei der normalen while-Schleife wird vor dem Ausführen des Codes die Schleifenbedingung überprüft, während bei der do-while-Schleife zuerst der Code ausgeführt und erst danach die Schleifenbedingung überprüft wird. Auf diese Weise kann man erzwingen, dass Anweisungen innerhalb der Schleife auf jeden Fall mindestens einmal ausgeführt werden, auch wenn sich die Schleifenbedingung gleich am Anfang als unwahr herausstellt.
let x = 10;
do {
document.querySelector('output').innerHTML = "x × x = " + (x * x);
x = x + 1;
} while (x < 10);
Und einmal so:
let x = 10;
while (x < 10) {
document.querySelector('output').innerHTML = "x * x = " + (x * x);
x = x + 1;
}
In den Beispielen werden jeweils ein kleiner JavaScript-Bereich definiert. In beiden Bereichen wird eine Variable x
definiert und mit dem Wert 10
vorbelegt.
Im ersten Bereich wird solange das Quadrat von x
(das bei jedem Schleifendurchlauf um 1 erhöht wird) geschrieben, wie x
kleiner als 10
ist. Da x
ja schon am Beginn den Wert 10
hat, ist die Abbruchbedingung eigentlich schon von vorne herein erfüllt. Trotzdem wird einmal das Quadrat von x
ausgegeben, da die Überprüfung der Schleifenbedingung erst nach dem Ausführen der Anweisungen innerhalb der Schleife erfolgt.
Im zweiten Script-Bereich herrschen die gleichen Bedingungen, jedoch wird dort eine normale while-Schleife notiert. Da x
von vorne herein nicht kleiner als 10
ist, werden die Anweisungen der while-Schleife kein einziges Mal ausgeführt. Die Überprüfung der Schleifenbedingung, die am Anfang stattfindet, verhindert dies.
Schleifen mit "for"
Die for
-Schleife wird verwendet, wenn eine bestimmte Anzahl an Durchläufen benötigt wird. Der Schleifenkopf fasst die Vorbereitung der ganzen Schleife, die Schleifenbedingung und die nötigen Berechnungen zur Fortsetzung der Schleife zusammen.
Syntax:
for ( Initialisierung; Schleifenbedingung; Fortsetzung) { Anweisungen }
Du notierst also zunächst das Schlüsselwort for
und öffnest eine runde Klammer. Es folgen drei Code-Stücke, die die Schleife steuern und durch ein Semikolon voneinander getrennt werden. Danach notierst du eine schließende runde Klammer und es folgen eine oder mehrere Anweisungen, die durch geschweifte Klammern zusammengefasst werden.
- Initialisierung
- Sie geschieht als erstes, und nur ein einziges Mal. Hier kann man beispielsweise einen Zähler auf seinen Anfangswert setzen.
- Schleifenbedingung
- Sie wird vor jedem Schleifendurchlauf ausgewertet und muss
true
liefern, damit der Schleifenrumpf ausgeführt wird. Man kann hier prüfen, ob der Endwert des Zählers noch nicht erreicht ist. - Fortsetzung
- Sie wird nach jedem Schleifendurchlauf ausgewertet. Ihr Zähler könnte hier um 1 erhöht werden.
Grundsätzlich handelt es sich bei der For-Schleife um eine Form von syntaktischem Zucker, denn man könnte, von einer Kleinigkeit abgesehen, auf die wir noch eingehen, die for-Schleife genauso gut als while-Schleife aufschreiben:
Ersatzschreibweise
Initialisierung; while (Schleifenbedingung) { Anweisungen; Fortsetzung; }
Im häufigsten Fall wird die for-Schleife mit einer Zählervariablen gesteuert. Im Initialisierungsteil wird die Zählervariable auf einen Anfangswert gesetzt, in der Schleifenbedingung wird geprüft, ob der Endwert noch nicht erreicht ist und in der Fortsetzung wird die Zählervariable auf den nächsten Wert gesetzt (das kann eine Erhöhung um 1 sein, es kann aber auch jede andere sinnvolle Änderung stattfinden).
let htmlText = "";
for (let i = 10; i <= 36; i++) {
htmlText = htmlText + '<span style="font-size:' + i + 'px">Schrift mit ' + i + ' Pixel Größe</span><br>';
}
document.querySelector('output').innerHTML = htmlText;
Das Beispiel definiert eine Stringvariable namens htmlText
, in der im Verlauf einer for-Schleife eine Folge von <span>
Elementen gesammelt wird. Am Ende wird das so entstandene HTML-Fragment als HTML-Inhalt in ein <output>
-Element geschrieben, so dass der Browser es anzeigt.
Der Initialisierungsteil der for-Schleife lautet let i = 10
. Hier wird also eine Variable i
deklariert und auf den Wert 10 initialisiert. Die Schleifenbedingung ist i <= 36
, die Schleife endet also, wenn i
den Wert 36 überschreitet. Zur Fortsetzung wird die Variable i
mit Hilfe des Inkrement Operators ++
um 1 erhöht (das Ergebnis des Operators ist irrelevant, man kann gleichermaßen Prä- oder Post-Inkrement verwenden).
Damit durchläuft i die Werte von 10 bis 36 und die Schleife erzeugt HTML für 27 <span>
Elemente, in denen die Schriftgröße von 10x bis 36px variiert. Der erzeugte HTML Text wird mit Hilfe des + Operators zu einer langen Zeichenkette zusammengebaut. Zum Verständnis der zusammengesetzten Teile bei Ausgabe
siehe auch . Danach ist die Schleife zu Ende.
An dieser Stelle findet sich der Unterschied von for
und while
. Dadurch, dass die Deklaration von i mit let innerhalb der for
-Klammer stattfand, ist der Gültigkeitsbereich dieser Deklaration auf den Schleifenrumpf beschränkt. Man kann deshalb hinter der Schleife auf i
nicht mehr zugreifen. Mit while
kann man diesen Effekt nicht erreichen. Wenn du nach der Schleife noch auf i zugreifen möchten, musst du die Deklaration außerhalb der Schleife vornehmen.
let htmlText = "";
let i;
for (i = 10; i <= 36; i++) {
htmlText = htmlText + '<span style="font-size:' + i + 'px">Schrift mit ' + i + ' Pixel Größe</span><br>';
}
document.querySelector('output').innerHTML = htmlText;
console.log(i); // Funktioniert.
Tatsächlich bist du bei der for-Schleife aber nicht darauf angewiesen, mit einer Zählervariablen zu arbeiten. Man kann jede Form von Initialisierung, Schleifenbedingung und Fortsetzung notieren, die auch in einer while-Schleife verwendet werden kann. Beispielsweise könnte man eine verkettete Liste durchlaufen:
for (let zeiger = liste; zeiger != null; zeiger = zeiger.next) {
console.log("Listenelement hat den Wert " + zeiger.wert);
}
Enumerationsschleifen mit "for...in"
Eine spezielle Form der for
-Schleife ist die for...in
-Schleife. Sie dient nicht zur Konstruktion allgemeiner Schleifen, sondern dazu, alle Eigenschaften eines Objekts aufzulisten (zu „enumerieren“).
Syntax:
for( Variable in Objekt ) { Anweisungen }
JavaScript durchläuft mit einer solchen Schleife die Eigenschaften des Objekts, das hinter in
angegeben ist, inklusive derjenigen, die über die Prototypenkette geerbt werden. Dabei werden aber nicht alle Eigenschaften verarbeitet. Eine Objekteigenschaft diese Bedingungen erfüllen, um von for...in erfasst zu werden:
- Ihr Schlüssel muss ein String oder ein Arrayindex sein. Eigenschaften, deren Schlüssel ein Symbol ist, können zwar laut Propertydescriptor enumerierbar sein, werden von for...in aber übergangen. Arrayindexe werden aber nicht als Zahl, sondern ebenfalls als String geliefert.
- Sie muss zum ersten Mal angetroffen werden. Diese Bedingung ist von Bedeutung, wenn
for...in
die Prototypkette verarbeitet. Enthält ein Prototypobjekt eine Eigenschaft, die unter gleichem Namen schon vorher angetroffen wurde, so wird sie vonfor...in
nicht erneut aufgelistet. - Sie muss als aufzählbar deklariert sein, d.h. in ihrem Propertydescriptor muss die
enumerable
-Eigenschaft auftrue
gesetzt sein. Jedes Objekt erbt Eigenschaften vonObject.prototype
, aber weil diese nicht als aufzählbar deklariert sind, gibt einefor...in
Schleife sie nicht aus.
Die Reihenfolge, in der for...in
die Eigenschaften liefert, ist von der JavaScript-Spezifikation definiert. Falls ein Array durchlaufen wird, so werden zunächst alle Array-Indexe in numerisch aufsteigender Reihenfolge geliefert. Danach folgen die übrigen Eigenschaften, und zwar in der Reihenfolge in der sie am Objekt gespeichert wurden.
let Ausgabe = "";
for (let Eigenschaft in document) {
Ausgabe = Ausgabe + "document." + Eigenschaft + ": " + document[Eigenschaft] + "<br>";
}
document.querySelector('output').innerHTML = Ausgabe;
Das Beispiel zeigt, wie du mit Hilfe einer for...in
-Schleife einiges über die JavaScript-Fähigkeiten deines Browsers herausbekommen kannst. Im Beispiel werden die Eigenschaften des Objektes document
ausgegeben. Jeder Schleifendurchgang liefert den Namen einer Eigenschaft von document
. Mit Hilfe von document[Eigenschaft]
, der Indexschreibweise für den Zugriff auf Objekteigenschaften, wird der Wert dieser Eigenschaft bestimmt. Name und Wert werden an die Variable Ausgabe
angehängt. Sind alle Eigenschaften durchlaufen, endet die Schleife und die gefundenen Informationen werden als HTML Inhalt in ein <output>
-Element geschrieben.
for...in
benutzt, um über ein Array zu iterieren (Array-Indizes sind Eigenschaften des Arrays), erhältst du nicht nur die Array-Inhalte. Die length
-Eigenschaft des Arrays ist zwar nicht enumerierbar, aber die Methoden von Array.prototype
sind es und for...in
liefert sie mit. Hier ist die for...of
-Schleife die geeignetere Wahl.Iterationsschleifen mit "for...of"
Die for...of
-Schleife dient dazu, alle Elemente eines iterierbaren Objekts zu durchlaufen. Das sind diejenigen Objekte, die einen Iterator bereitstellen. Dazu gehören zum Beispiel Arrays und Strings, aber auch Objekte des DOM wie NodeList. Im Artikel zu Iteratoren kann man auch sehen, dass for...of
tatsächlich nur Syntaktischer Zucker für die Verarbeitung eines Iterators ist.
Du erkennst ein iterierbares Objekt daran, dass es eine Methode besitzt, deren Schlüssel (nicht Name!) in Symbol.iterator
zu finden ist.
Syntax:
for( Variable of iterierbares Objekt ) { Anweisungen }
Beispielsweise liefert der Iterator eines Arrays alle Arrayelemente von Index 0 bis zum Index (length-1)
. Damit kann man die Werte eines Arrays bequem mit for...of
durchlaufen - allerdings ohne dabei den Index zu kennen, den ein bestimmtes Element hat. Dafür müsste man eine for-Schleife programmieren oder die forEach-Methode des Arrays verwenden. Du könntest auch einen eigenen Array-Iterator schreiben, der Index und Wert bereitstellt - im Abschnitt zu Generatoren findest du dafür ein Beispiel.
const numbers = [2, 3, 5, 7, 11, 13, 17, 19];
numbers.name = 'Primzahlen';
//for-in-Schleife
for (let i in numbers) {
console.log(i); // 0, 1, 2, 3, 4, 5, 6, 7, name
}
//for-of-Schleife
for (let i of numbers) {
console.log(i); // 2, 3, 5, 7, 11, 13, 17, 19
}
Du siehst, dass man mit for...in
die Namen der Objekteigenschaften erhält - wozu im Falle eines Arrays auch die Arrayindizes gehören (die length
-Eigenschaft eines Arrays ist als nicht aufzählbar gekennzeichnet und fehlt deshalb). Dagegen durchläuft for...of
die Werte, die der in Arrays eingebauten Iterator liefert, das sind die Arrayinhalte.
Die for...of-Schleife eignet sich für Strings, Arrays und Maps, NodeLists und generell alle Objekte, die über einen Iterator verfügen.
Der Map-Iterator hat die Besonderheit, dass er dir nicht nur den gespeicherten Wert liefert, sondern ein Array mit zwei Einträgen: Dem Schlüssel und dem Wert. Du kannst mit Hilfe der Destrukturierung bequem darauf zugreifen.
let map = new Map();
map.set("self", "HTML");
map.set("world", "Wide Web");
for (let [key, value] of map) {
console.log(`Schlüssel: ${key} - Wert: ${value}`);
}
Kontrolle innerhalb von Schleifen - break und continue
Schleifen sind kritische Faktoren innerhalb eines Scripts. Bei komplizierteren Aufgaben ist es manchmal nicht einfach, eine Schleife so zu programmieren, dass das Verlassen der Schleife über die normale Schleifenbedingung möglich ist. Das heißt nicht, dass das unmöglich ist, es führt dann aber zu aufwändigen Abfragen und dementsprechend tiefer Schachtelung von Logik innerhalb der Schleife. Die Möglichkeit, eine Schleife gezielt zu verlassen oder zumindest den aktuellen Durchlauf abzubrechen, kann das Programm leichter lesbar machen.
break
Mit der break
-Anweisung kann man eine Schleife sofort beenden. Dazu musst du innerhalb des Schleifenkörpers das Wort break
als Anweisung notieren. Natürlich ergibt das nur dann einen Sinn, wenn die break
-Anweisung von einer Abfrage abhängt, andernfalls würde die Schleife immer im ersten Durchlauf abgebrochen.
let daten = [ 1, 3, 9, 0, 5, 2 ];
let i, summe = 0;
for (i=0; i < daten.length; i++) {
summe = summe + daten[i];
if (summe > 10) {
break;
}
console.log(`i = ${i}, summe = ${summe}`);
}
console.log(`Grenzwert erreicht bei i=${i}, summe=${summe}`);
Im Beispiel werden die Elemente eines Arrays aufaddiert. Sobald die Summe den Wert 10 überschreitet, wird die Schleife abgebrochen. Ohne break
müsste man die Summenbedingung zweimal prüfen: Einmal, um den Rest des Schleifenrumpfs zu überspringen, und dann noch einmal in der Schleifenbedingung.
continue
Die continue
-Anweisung ist die kleine Schwester von break
. Sie bricht nicht die gesamte Schleife ab, sondern nur den aktuellen Schleifendurchlauf. Durch continue
wird die Verarbeitung der Anweisungen des Schleifenrumpfs abgebrochen. In einer mit while
oder do...while
gebildeten Schleife folgt nach continue
die Prüfung der Schleifenbedingung und bei Erfolg der nächste Durchlauf. In einer mit for
gebildeten Schleife wird vor dem Prüfen der Schleifenbedingung auch noch die erforderliche Logik zur Fortsetzung der Schleife ausgeführt.
let daten = [ 1, 3, 9, 0, 15, 2 ];
let anzahl = 0,
summe = 0;
for (let wert of daten) {
if (wert <= 5)
continue;
anzahl++;
summe = summe + wert;
}
console_log(`Die Summe der ${anzahl} Werte über 5 ist ${summe}`);
Dieses Beispiel verwendet eine for..of
-Schleife, um ein Array zu durchlaufen. Nur die Werte über 5 sollen berücksichtigt werden. Deshalb beginnt die Schleife mit einer Prüfung, ob der aktuelle Wert kleiner oder gleich 5 ist. Ist das der Fall, wird der Rest der Schleife mit continue
übersprungen. Für Werte über 5 wird ein Zähler erhöht und der Wert aufaddiert.
Dieses Beispiel ist zu klein, um den Lesbarkeitsgewinn durch continue
augenfällig zu machen. Natürlich könnte man die Bedingung auch herumdrehen (wert > 5
) und die beiden Anweisungen zum Zählen und Addieren als Anweisungsblock der Bedingung unterordnen. Die Verwendung von continue
wäre dann nicht nötig. Denke aber an eine Schleife, wo es mehrere Fälle geben kann, die zum Überspringen des restlichen Schleifenrumpfes führt. Oder wo die Erkenntnis, dass der Rest des Rumpfes nicht ausgeführt werden darf, erst in einer tieferen Schachtelung von Bedingungen gewonnen wird. Ohne continue
wird es dann deutlich komplizierter.
Es soll aber nicht verschwiegen werden, dass man durch eine Umstrukturierung des Programms oft auf continue
verzichten und eventuell die Lesbarkeit sogar verbessern kann. Wenn in einer Schleife eine komplexe Logik nötig ist, um zu erkennen, ob die eigentliche Schleifenverarbeitung überhaupt ausgeführt werden darf, dann sollte man überlegen, ob man die Prüflogik nicht in eine Funktion auslagern sollte, die true
zurückgibt, wenn die Verarbeitung zulässig ist:
let daten = [ 1, 3, 9, 0, 15, 2 ];
let anzahl = 0,
summe = 0;
for (let wert of daten) {
if (istVerarbeitbar(wert))
{
anzahl++;
summe = summe + daten;
}
}
console.log(`Die Summe der ${anzahl} Werte über 5 ist ${summe}`);
function istVerarbeitbar(wert) {
return wert > 5;
}
Natürlich gilt auch hier die Einschränkung vom vorherigen Beispiel: es ist zu klein, um den Nutzen zu zeigen. Stell dir sich aber vor, dass istVerarbeitbar
eine Funktion mit 20 Zeilen oder mehr wäre. In diesem Fall hilft es der Lesbarkeit deutlich, dass die eigentliche Schleife nicht von diesen Zeilen aufgeblasen wird.
break und continue mit Label
Programme, in denen mehrere Schleifen ineinander geschachtelt werden, kommen vor. Und es kann dann auch vorkommen, dass in einer der inneren Schleifen festgestellt wird, dass eine weiter außen liegende Schleife abgebrochen werden muss, oder zumindest der aktuelle Durchlauf zu beenden ist.
Ein weiteres Szenario ist, dass eine Schleife eine switch
-Anweisung enthält. Innerhalb von switch
ist das break
-Statement erforderlich, um den ausgewählten Fall abzuschließen. Aber was, wenn im switch
festgestellt wird, dass die Schleife verlassen werden muss? Das break
beendet ja nur den switch
!
Man kann solche Fälle mit Hilfe von Schaltervariablen lösen, die man in der inneren Stufe setzt und in der äußeren Stufe abfragt. Das macht den Code aber auch nicht besser verständlich. Als eine Alternative dazu bietet JavaScript die Möglichkeit, einer Schleifenanweisung (und auch switch
) einen Namen geben. Solche Namen heißen traditionell Label, weil sie sozusagen ein Etikett an der Anweisung sind, mit dem man angeben kann: Ich beziehe mich auf diese Anweisung. Ein Label besteht aus einem JavaScript-Bezeichner, gefolgt von einem Doppelpunkt.
Der break
- oder continue
-Anweisung kann der Name des Labels als Argument hinzugefügt werden. Damit weiß JavaScript, was verlassen oder übersprungen werden soll.
break
beziehungsweise continue
und dem Label. let daten = [ 1, 3, 9, 0, 15, 2 ];
let anzahl = 0,
summe = 0;
summierung:
for (let wert of daten) {
switch(wert) {
case 0:
summe = summe * 2;
break summierung;
case 1:
summe = summe + 1000;
continue summierung;
default:
anzahl++;
summe = summe + wert;
break;
}
}
console.log(`Die Summe der ${anzahl} Werte über 5 ist ${summe}`);
Die merkwürdige Logik dieses Programms sieht vor, dass der Wert 0 im Datenarray die bisherige Summe verdoppeln und dann die Summierung abbrechen soll. Der Wert 1 soll als 1000 summiert, aber nicht gezählt werden. Alle anderen Werte werden gezählt und aufaddiert.
Um die Schleife gezielt verlassen zu können, hat sie das Label summierung:
bekommen.
Die Steuerung erfolgt über eine switch
-Anweisung.
- Im
case 0:
Zweig wird mitbreak summierung;
bewirkt, dassswitch
- undfor
-Anweidung verlassen werden. - Im
case 1:
Zweig fordert die Anweisungcontinue summierung;
, dass der Rest des aktuellen Schleifendurchlaufs zu überspringen ist. Für dieswitch
-Anweisung wirkt diesercontinue
wie einbreak
. - In allen anderen Fällen wird gezählt und addiert. Die dann folgende
break
-Anweisung steht ohne Angabe eines Labels und bezieht sich deshalb auf die Kontrollanweisung, der sie direkt untergeordnet ist: dieswitch
-Anweisung.
Asynchrone Iteration mit for await ... of
Wenn du einen async-Generator iterieren möchtest, erhältst du in jedem Iterationsschritt ein Promise, für das man entweder einen then-Handler installiert oder dessen Ergebnis mit await erwarten muss.
Die for await...of Schleife nimmt dir diesen Schritt ab, und sie erkennt auch, ob der gelesene Iterator synchron oder asynchron ist. Du musst nicht unterscheiden, welche Form von Iterator dir vorliegt.
Die for await...of Schleife darfen man natürlich nur dort verwenden, wo ein await zulässig ist, also in einer async-Funktion oder auf globaler Ebene eines ECMAScript-Moduls.
function *getValues() {
for (let i=0; i<10; i++)
yield Promise.resolve(i);
}
for await (const value of getValues()) {
console.log(value);
}
Weblinks
- ↑ ECMAScript 2015 (6th Edition, ECMA-262): If-Statement
- ↑ MDN: If-Statement
- ↑ ECMAScript 2015 (6th Edition, ECMA-262): Switch-Statement
- ↑ MDN: Switch-Statement