mitmario.dev

Schleifen ineinander

JavaScript Im Browser 3 Min Lesezeit 4 BeispieleLektion 4 von 6

Alles, was ein Raster hat, braucht zwei Schleifen: eine Tabelle, ein Spielfeld, ein Kalender. Die eine geht die Zeilen durch, die andere die Spalten.

Wie oft läuft das?

Die innere läuft für jeden Durchlauf der äußeren
let durchlaeufe = 0;

for (let aussen = 1; aussen <= 3; aussen++) {
  console.log("Äußere Runde", aussen, "beginnt");

  for (let innen = 1; innen <= 4; innen++) {
    durchlaeufe++;
  }

  console.log("  Die innere ist viermal gelaufen");
}

console.log("Insgesamt:", durchlaeufe, "Durchläufe");

Das ist die Frage, die man beim ersten Mal falsch beantwortet.

Die innere Schleife läuft vollständig für jeden einzelnen Durchlauf der äußeren. Sie beginnt jedes Mal von vorn. Bei einer äußeren mit 3 Runden und einer inneren mit 4 sind das nicht 7 Durchläufe, sondern 12. Die Zahlen multiplizieren sich.

Wenn du das nicht glaubst, ist der Reiter Debug hier das kürzeste Argument: Zeichne das Beispiel auf und blätter durch. Solange aussen=1 steht, zählt innen von 1 bis 4 durch, und erst danach springt aussen auf 2.

Daraus folgt etwas Praktisches: Verschachtelte Schleifen werden schnell teuer. Bei 10 mal 10 sind es 100 Durchläufe, bei 1000 mal 1000 sind es eine Million. Wenn also eine Seite plötzlich hängt, ist die erste Frage, ob dort zwei Schleifen ineinanderliegen und wie groß die Zahlen darin sind.

Für die Tabelle, die du gleich baust, ist das kein Thema. Ab drei Ebenen und größeren Zahlen wird es eines, und dann schneidet man das Problem anders. Wie, das ist ein eigenes Fachgebiet und nicht Teil dieses Kurses.

Das Muster für ein Raster

Ein Raster bauen
// Außen die Zeile, innen die Spalte. Das ist die übliche Aufteilung.
for (let zeile = 1; zeile <= 3; zeile++) {
  let text = "";

  for (let spalte = 1; spalte <= 4; spalte++) {
    text += zeile * spalte + " ";
  }

  // Erst nach der inneren Schleife wird die Zeile ausgegeben.
  console.log(text);
}

Die übliche Aufteilung lautet: außen die Zeile, innen die Spalte.

Der Grund dafür ist die Ausgabe. Eine Zeile ist erst fertig, wenn alle ihre Spalten abgearbeitet sind. Also sammelst du sie in der inneren Schleife zusammen und gibst sie aus, sobald die innere Schleife fertig ist.

Achte darauf, wo let text = "" steht: innerhalb der äußeren Schleife, außerhalb der inneren. Damit bekommt jede Zeile ihren eigenen leeren Text. Legst du ihn ganz oben an, wächst er über alle Zeilen hinweg zu einem einzigen langen Wurm zusammen.

Das ist dieselbe Regel wie beim Aufsummieren aus Lektion 4.1, nur eine Ebene tiefer: Der Sammelbehälter steht genau eine Stufe über der Schleife, die ihn füllt.

Zwei Zähler, zwei Namen

Zwei Zähler brauchen zwei Namen
// Beide Schleifen benutzen i. Das läuft sogar, weil die innere ihr
// eigenes i bekommt. Beim Lesen weiß trotzdem niemand mehr, welches
// i gerade gemeint ist.
for (let i = 1; i <= 2; i++) {
  let text = "";
  for (let i = 1; i <= 3; i++) {
    text += i;
  }
  console.log("Innen ergab", text, "und außen steht i auf", i);
}

console.log("---");

// Mit sprechenden Namen ist die Frage gar nicht erst da.
for (let zeile = 1; zeile <= 2; zeile++) {
  let text = "";
  for (let spalte = 1; spalte <= 3; spalte++) {
    text += spalte;
  }
  console.log("Zeile", zeile, "ergab", text);
}

Im ersten Block heißen beide Zähler i. Das ist erlaubt und läuft sogar richtig, weil die innere Schleife ihr eigenes i bekommt und das äußere so lange verdeckt.

Lesbar ist es nicht. Beim Blick auf text += i musst du erst herausfinden, welches der beiden gemeint ist, und wenn du die innere Schleife später entfernst, ändert dieselbe Zeile still ihre Bedeutung.

Im Reiter Debug lässt sich das ansehen, und es ist eine der wenigen Stellen, an denen man die Verdeckung wirklich sieht: In der inneren Schleife zählt i bis 3, und in der Zeile direkt danach steht wieder i=1. Nicht zurückgesetzt, sondern ein anderer Name mit demselben Wortlaut. Zeichne das Beispiel einmal auf und blätter zur Zeile mit console.log, die nach der inneren Schleife steht.

Nimm sprechende Namen, sobald zwei Schleifen ineinanderliegen. zeile und spalte, oder reihe und feld, oder was sonst zum Problem passt. Das Kürzel i ist eine Redewendung für eine einzelne kurze Schleife, nicht für ein Raster.

break und continue in verschachtelten Schleifen

break wirkt nur auf die innerste
for (let zeile = 1; zeile <= 3; zeile++) {
  for (let spalte = 1; spalte <= 5; spalte++) {
    if (spalte === 3) {
      break;
    }
    console.log("Zeile", zeile, "Spalte", spalte);
  }
}

console.log("Die äußere Schleife lief trotzdem dreimal komplett durch.");

Beide wirken auf die innerste Schleife, in der sie stehen. Ein break in der inneren beendet nur die innere; die äußere macht mit ihrem nächsten Durchlauf weiter, und die innere fängt wieder von vorn an.

Im Beispiel bricht die innere Schleife jedes Mal bei Spalte 3 ab, und trotzdem gibt es drei Zeilen.

Wenn du wirklich beide beenden willst, brauchst du entweder eine zusätzliche Bedingung in der äußeren Schleife oder, deutlich besser, eine eigene Funktion mit return. Es gibt in JavaScript auch benannte Sprungmarken, mit denen ein break mehrere Ebenen verlässt. Sie kommen so selten vor, dass die meisten sie nicht kennen, und wo sie stehen, ist eine Funktion fast immer die bessere Antwort.

Zum Mitnehmen

Die innere Schleife läuft vollständig für jeden einzelnen Durchlauf der äußeren. Bei 10 mal 10 sind das 100 Durchläufe, nicht 20.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.