Ein Ritt durch Iterationen

Javascript und seine Schleifen-Typen

31.10.2023

javascripttutorialhowtode
Gregor Wedlich
Gregor Wedlich
Life, the Universe and Everything.
Donate with:
Lightning
Alby

Inhaltsverzeichnis

    Es gibt unzählige Beiträge im Internet, die erklären, welche Schleife man wann und wo in der JavaScript-Programmierung nutzt. Auch ich werde hier nichts Neues erzählen; ich hatte aber einfach Lust, eine kleine Übersicht zu erstellen.

    Ich werde versuchen, auf alle mir gängigen Schleifenarten einzugehen. Manche davon wird man aber so gut wie nie nutzen, und manche umso mehr. Es ist aber immer gut alle mal gesehen zu haben.

    For-Schleife

    Die "for"-Schleife ist eine sehr flexible und effizente Schleifenstruktur und wird daher gerne bei Szenarien eingesetzt wo es um Geschwindigkeit geht.

    Pro:

    • Wenn die Anzahl der Iterationen im Voraus bekannt ist.
    • Du über einen Index iterieren möchtest, um zb. auf Elemente zuzugreifen
    • Die Logik einfach und direkt ist
    • Wenn es um Perfomance geht

    Kontra:

    • Wenn die Anzahl der Iterationen nicht bekannt ist, hier sind "while" oder "do-while"-Schleifen die bessere Wahl
    • Wenn du über eigenschaften eines Objektes iterieren möchtest, hier macht eine "for-of" -Schleife oder Array-Methoden wie "forEach", "map" usw. mehr sinn
    • Wenn du die funktionale Programmierung bevorzugst und die Schleifenlogik in eine Separate Funktion auslagern möchtest.

    Schaut folgend aus:

    1for (let i = 0; i < 10; i++) { 2 console.log(i); 3} 4 5// Output: 0 1 2 3 4 5 6 7 8 9

    Beispiel:

    Angenommen, du hast einen Obststand und möchtest den Gesamtpreis aller verfügbaren Früchte berechnen. Du hast eine Liste von Früchten und jedem ist ein Preis zugeordnet. Eine for-Schleife ist in diesem Fall sinnvoll, da sie dir ermöglicht, durch die Liste zu gehen und die Preise zusammenzuzählen.

    1const fruits = [ 2 { name: "Apple", price: 1 }, 3 { name: "Banana", price: 0.5 }, 4 { name: "Cherry", price: 0.2 }, 5]; 6 7let totalCost = 0; 8 9for (let i = 0; i < fruits.length; i++) { 10 totalCost += fruits[i].price; 11} 12 13console.log("Total cost:", totalCost); 14 15// Output: Total cost: 1.7

    For-In-Schleife

    Die "for-in"-Schleife zeigt ihre Stärke in dem sie einfach und direkt durch Eigenschaften eines Objekts iteriert um zb. den Schlüssel und den Wert jeder Eigenschaft auszulesen. Bei der Iteration von Arrays oder wenn die Performance kritisch ist sollte nicht auf diese Schleifenart zurückgegriffen werden.

    Pro:

    • Einfachheit: Einfach zu verstehen und zu schreiben, wenn du über die Eigenschaften eines Objekts iterieren möchtest.
    • Direkter Zugriff auf Schlüssel und Werte: Du erhältst direkten Zugriff auf den Schlüssel (Eigenschaftsnamen) und den Wert jeder Eigenschaft.

    Kontra:

    • Die for-in-Schleife kann langsamer sein als andere Schleifenarten, was in performancekritischen Anwendungen problematisch sein kann.
    • Garantiert keine bestimmte Reihenfolge der Iteration, was problematisch sein kann, wenn die Reihenfolge wichtig ist.
    • Die for-in-Schleife ist nicht ideal für die Iteration über Arrays, da sie Indexe als Strings behandelt und auch ererbte Eigenschaften durchläuft.

    Schaut folgend aus:

    1const obj = {a: 1, b: 2, c: 3}; 2for (let key in obj) { 3 console.log(key, obj[key]); 4} 5 6// Output: a 1, b 2, c 3

    Beispiel:

    Angenommen, du hast ein Objekt, das die Preise verschiedener Früchte in einem Obstladen zeigt und du über das Objekt iterieren möchtest um eine Preisliste zu erstellen.

    1const fruitPrices = { apple: 1, banana: 0.5, cherry: 0.2 }; 2 3let priceList = ""; 4for (let fruit in fruitPrices) { 5 priceList += `${fruit}: ${fruitPrices[fruit]}€\n`; 6} 7 8console.log(priceList); 9 10// Output: apple: 1€, banana: 0.5€, cherry: 0.2€

    For-Of-Schleife

    Die for-of-Schleife ist eine ausgezeichnete Wahl, wenn du über die Elemente eines iterierbaren Objekts iterieren und eine einfache, klare Syntax verwenden möchtest. Wenn du jedoch den Index benötigst oder über die Eigenschaften eines Objekts iterieren möchtest, könnten andere Schleifenarten oder Methoden besser geeignet sein.

    Pro:

    • Die for-of-Schleife bietet eine klare und leicht verständliche Syntax für die Iteration über iterierbare Objekte.
    • Im Gegensatz zur traditionellen for-Schleife, die den Index verwendet, gibt for-of direkten Zugriff auf die Elemente des iterierbaren Objekts.
    • Im Gegensatz zur for-Schleife benötigt for-of keine manuelle Indexverwaltung, was Fehler vermeiden kann.

    Kontra:

    • Wenn du den Index des aktuellen Elements benötigst, ist die for-of-Schleife nicht ideal, da sie den Index nicht direkt zur Verfügung stellt.
    • Die for-of-Schleife bietet nicht die gleiche Flexibilität wie die for-Schleife, wenn es um komplexe Abbruchbedingungen oder Iterationslogik geht.
    • Die for-of-Schleife kann nicht direkt auf Objekte angewendet werden, da sie nicht iterierbar sind.

    Schaut folgend aus:

    1const arr = ["apple", "banana", "cherry"]; 2for (let value of arr) { 3 console.log(value); 4} 5 6// Output: apple, banana, cherry

    Beispiel:

    In diesem Fall ist der Entwurf schon das Beste Beispiel. Wir gehen einfach durch die Liste und geben die Früchte aus.

    While-Schleife

    EIne gute Wahl wenn die Anzahl der Iterationen im Voraus nicht bekannt sind und du eine Aktion wiederholt ausführen möchtest solange eine Bedingung erfüllt ist.

    Pro:

    • Die Bedingung wird vor jedem Durchgang der Schleife überprüft, was eine klare Kontrolle darüber bietet, wann die Schleife beendet wird.
    • Wenn die Anzahl der benötigten Iterationen im Voraus nicht bekannt ist, ist die while-Schleife eine gute Wahl.

    Kontra:

    • Wenn die Bedingung immer wahr ist kann es zu Endlosschleifen kommen
    • Du musst Bedingung und Logik innerhalb der Schleife manuell verwalten damit sie irgendwan beendet wird
    • Du kannst damit nicht auf einen index zugreifen

    Schaut folgend aus:

    1let i = 0; 2while (i < 10) { 3 console.log(i); 4 i++; 5} 6 7// Output: 0 1 2 3 4 5 6 7 8 9

    Beispiel:

    Angenommen, du hast einen Obstkorb und du möchtest die Früchte daraus entfernen, bis der Korb leer ist. Eine while-Schleife ist hier nützlich, da sie weiterläuft, solange eine bestimmte Bedingung erfüllt ist - in diesem Fall, solange noch Früchte im Korb sind.

    1let fruitBasket = ["apple", "banana", "cherry"]; 2let fruit; 3 4while (fruitBasket.length > 0) { 5 fruit = fruitBasket.pop(); // Entfernt die letzte Frucht aus dem Korb 6 console.log(`Removing ${fruit} from the basket.`); 7} 8 9console.log("The basket is empty."); 10 11/* 12Output: 13 14Removing cherry from the basket. 15Removing banana from the basket. 16Removing apple from the basket. 17The basket is empty. 18 19*/

    Do-While-Schleife

    Wenn du sicherstellen möchtest das ein Codeblock mindestens einmal ausgeführt werden soll ist die "do-while"-Schleife eine gute Wahl.

    Pro:

    • Der Codeblock in der do-while-Schleife wird mindestens einmal ausgeführt, unabhängig von der Bedingung.

    Kontra:

    • Wie auch schon bei der "while"-Schleife kann es zu Endlosschleifen kommen solange eine Bedingung wahr ist.
    • Du musst Bedingung und Logik innerhalb der Schleife manuell verwalten damit sie irgendwan beendet wird
    • Du kannst damit nicht auf einen index zugreifen

    Schaut folgend aus:

    1let i = 0; 2do { 3 console.log(i); 4 i++; 5} while (i < 10); 6 7// Output: 0 1 2 3 4 5 6 7 8 9

    Beispiel:

    Eine Verkäufer:in, möchte Früchte verkauft, und du möchtest sicherstellen, dass mindestens eine Transaktion durchgeführt wird, bevor der Stand für den Tag schließt.

    1let fruitSold = 0; 2let hasMoreFruitsToSell = true; 3 4do { 5 fruitSold++; 6 console.log(`Fruit ${fruitSold} sold.`); 7 // Hier könnte eine Bedingungsprüfung stehen, um festzustellen, ob noch mehr Früchte zu verkaufen sind. 8 // Zum Zweck dieses Beispiels setzen wir hasMoreFruitsToSell einfach auf false, um die Schleife zu beenden. 9 hasMoreFruitsToSell = false; 10} while (hasMoreFruitsToSell); 11 12console.log("No more fruits to sell for today."); 13 14// Output: Fruit 1 sold. No more fruits to sell for today.

    Array.prototype.forEach()

    Wenn du eine einfache und klare Syntax für die Iteration über ein Array benötigst und eine Funktion auf jedes Element anwenden möchtest ist "Array.prototype.forEach()" eine gute Wahl.

    Pro:

    • Bietet eine klare und leicht verständliche Syntax für die Iteration über ein Array.
    • Direkter Zugriff auf das aktuelle Element und seinen Index im Array.
    • Benötigt keine manuelle Indexverwaltung wie zb. bei "for"-Schleifen

    Kontra:

    • Du kannst die Iteration nicht vorzeitig beenden, wie es mit einer for-Schleife oder einer for...of-Schleife und einer break-Anweisung möglich wäre.
    • Wenn es um komplexe Iterationslogik oder Abbruchbedingungen geht, liefert die forEach-Schleife nicht die gleiche Flexibilität
    • Nicht ideal für Asynchrone Funktionen da sie nicht auf Promises wartet

    Schaut folgend aus:

    1const arr = [1, 2, 3]; 2arr.forEach((value, index) => { 3 console.log(index, value); 4}); 5 6// Output: 0 1, 1 2, 2 3

    Beispiel:

    Du hast eine Liste von Früchten und möchtest die Länge jedes Fruchtnamens berechnen. Die forEach() Methode ist in diesem Fall nützlich, da sie eine klare und einfache Syntax bietet, um eine Funktion auf jedes Element in einem Array anzuwenden.

    1const fruits = ["apple", "banana", "cherry"]; 2 3fruits.forEach(function (fruit) { 4 const nameLength = fruit.length; 5 console.log(`The name ${fruit} has ${nameLength} characters.`); 6}); 7 8/* 9Output: 10 11The name apple has 5 characters. 12The name banana has 6 characters. 13The name cherry has 6 characters. 14*/

    Array.prototype.map()

    Die Methode map() ist die Perfekte Wahl, wenn du ein neues Array auf Basis des ursprünglichen Arrays erstellen möchtest, insbesondere wenn du eine klare Transformation für jedes Element im Array hast.

    Pro:

    • map() gibt ein neues Array zurück, anstatt das ursprüngliche Array zu ändern, was gut für die Unveränderlichkeit ist.
    • direkter Zugriff auf das aktuelle Element und seinen Index im Array.

    Kontra:

    • kann langsam sein, insbesondere bei großen Arrays ggf. ist dann eine for-Schleife die bessere Wahl
    • Da immer ein neues Array erstellt wird kann dies zu unerwünschten Speicherverbrauch führen

    Schaut folgend aus:

    1const arr = [1, 2, 3]; 2const newArr = arr.map((value) => value * 2); 3 4console.log(newArr); 5 6// Output: [2, 4, 6]

    Beispiel:

    Angenommen, du hast eine Liste von Früchten und möchtest eine neue Liste erstellen, die die Namen aller Früchte in Großbuchstaben enthält. Die Methode map() ist hier ideal, da sie dir ermöglicht, eine Funktion auf jedes Element in einem Array anzuwenden und ein neues Array mit den Ergebnissen zu erstellen.

    1const fruits = ["apple", "banana", "cherry"]; 2 3const uppercasedFruits = fruits.map(function (fruit) { 4 return fruit.toUpperCase(); 5}); 6 7console.log(uppercasedFruits); 8 9// Output: ['APPLE', 'BANANA', 'CHERRY']

    Array.prototype.filter()

    Die Methode filter() ist eine hervorragende Wahl, wenn du ein neues Array auf Basis einer Bedingung erstellen möchtest, die auf den Elementen des ursprünglichen Arrays geprüft wird. Sie ermöglicht es dir, eine klare und prägnante Syntax zu verwenden, um Elemente zu filtern, die eine bestimmte Bedingung erfüllen, und ein neues Array mit diesen Elementen zu erstellen.

    Pro:

    • Ändert das ursprüngliche Array nicht, sondern erstellt ein neues Array, das die Bedingung erfüllt.
    • Direkter Zugriff auf das aktuelle Element und seinen Index im Array.

    Kontra:

    • kann langsam sein, insbesondere bei großen Arrays ggf. ist dann eine for-Schleife die bessere Wahl
    • Da immer ein neues Array erstellt wird kann dies zu unerwünschten Speicherverbrauch führen

    Schaut folgend aus:

    1const arr = [1, 2, 3, 4]; 2const newArr = arr.filter((value) => value % 2 === 0); 3 4console.log(newArr); 5 6// Output: [2, 4]

    Beispiel:

    Angenommen, du hast eine Liste von Früchten und möchtest eine neue Liste erstellen, die nur die Früchte enthält, deren Namen mehr als 5 Zeichen haben. Die Methode filter() ist hier ideal, da sie es dir ermöglicht, ein neues Array zu erstellen, das auf einer bestimmten Bedingung basiert.

    1const fruits = ["apple", "banana", "cherry", "kiwi", "lemon"]; 2 3const longNamedFruits = fruits.filter(function (fruit) { 4 return fruit.length > 5; 5}); 6 7console.log(longNamedFruits); 8 9/* 10Output: 11 12['banana', 'cherry', 'lemon'] 13*/

    Array.prototype.reduce()

    Die Methode reduce() ist eine gute Wahl, wenn du ein Array zu einem einzelnen Wert reduzieren möchtest, insbesondere wenn du eine klare Transformation oder Berechnung über die Elemente im Array ausführen möchtest.

    Pro:

    • Extrem flexibel und kann für eine Vielzahl von Aufgaben verwendet werden, bei denen ein Array zu einem einzelnen Wert reduziert werden muss.
    • Wie map() und filter() ändert reduce() das ursprüngliche Array nicht sondern erstellt ein neues
    • Direkter Zugriff auf das aktuelle Element und seinen Index im Array.

    Kontra:

    • kann langsam sein, insbesondere bei großen Arrays ggf. ist dann eine for-Schleife die bessere Wahl
    • Da immer ein neues Array erstellt wird kann dies zu unerwünschten Speicherverbrauch führen

    Schaut folgend aus:

    1const arr = [1, 2, 3, 4]; 2const sum = arr.reduce((accumulator, currentValue) => { 3 return accumulator + currentValue; 4}, 0); 5console.log(sum); 6 7// Output: 10

    Beispiel:

    Angenommen, du hast eine Liste von Früchten und möchtest die Gesamtzahl der Zeichen aller Fruchtnamen berechnen. Die Methode reduce() ist hier ideal, da sie es dir ermöglicht, einen Akkumulatorwert über die Elemente im Array zu führen und einen einzelnen Endwert zu berechnen.

    1const fruits = ["apple", "banana", "cherry"]; 2 3const totalCharacterCount = fruits.reduce(function (acc, fruit) { 4 return acc + fruit.length; 5}, 0); 6 7console.log(totalCharacterCount); 8 9// Output: 17

    Array.prototype.find()

    Die Methode find() ist eine gute Wahl, wenn du das erste Element in einem Array finden möchtest, das eine bestimmte Bedingung erfüllt.

    Pro:

    • Du erhältst direkten Zugriff auf das erste Element, das die Bedingung erfüllt, ohne den Index kennen zu müssen.
    • Bietet eine klare und leicht verständliche Syntax für die Suche nach einem Element in einem Array.

    Kontra:

    • Gibt nur das erste Element zurück, welches die Bedingung erfüllt
    • Kein Index
    • Wenn kein Element gefunden wird gibt find() "undefined" zurück

    Schaut folgend aus:

    1const arr = [1, 2, 3, 4]; 2const found = arr.find((value) => value % 2 === 0); 3console.log(found); 4 5// Output: 2

    Beispiel:

    Angenommen, du hast eine Liste von Früchten und möchtest die erste Frucht finden, deren Name mehr als 5 Zeichen hat. Die Methode find() ist hier ideal, da sie es dir ermöglicht, ein Array zu durchsuchen und das erste Element zurückzugeben, das eine bestimmte Bedingung erfüllt.

    1const fruits = ["apple", "banana", "cherry"]; 2 3const longNamedFruit = fruits.find(function (fruit) { 4 return fruit.length > 5; 5}); 6 7console.log(longNamedFruit); 8 9// Output: 'banana'

    Array.prototype.findIndex()

    Die Methode findIndex() ist eine gute Wahl, wenn du den Index des ersten Elements in einem Array finden möchtest, das eine bestimmte Bedingung erfüllt.

    Pro:

    • Du erhältst direkten Zugriff auf den Index des ersten Elements, das die Bedingung erfüllt.

    Kontra:

    • Gibt nur den Index des ersten Elements zurück, das die Bedingung erfüllt, nicht die Indizes aller übereinstimmenden Elemente.
    • Wenn kein Element die Bedingung erfüllt, gibt findIndex() -1 zurück, was zu möglichen Fehlern führen kann, wenn nicht darauf geprüft wird

    Schaut folgend aus:

    1const arr = [1, 2, 3, 4]; 2const foundIndex = arr.findIndex((value) => value % 2 === 0); 3console.log(foundIndex); 4 5// Output: 1

    Beispiel:

    Angenommen, du hast eine Liste von Früchten und möchtest den Index der ersten Frucht finden, deren Name mehr als 4 Zeichen hat. Die Methode findIndex() ist hier ideal, da sie es dir ermöglicht, ein Array zu durchsuchen und den Index des ersten Elements zurückzugeben, das eine bestimmte Bedingung erfüllt.

    1const fruits = ["apple", "banana", "cherry"]; 2 3const longNamedFruitIndex = fruits.findIndex(function (fruit) { 4 return fruit.length > 4; 5}); 6 7console.log(longNamedFruitIndex); 8 9// Output: 0

    Object.keys()

    Ist eine nützliche Methode, wenn du eine Liste der Schlüssel eines Objekts benötigst. Es ist besonders nützlich, wenn du über die Schlüssel eines Objekts iterieren und auf die Werte zugreifen möchtest, oder wenn du einfach nur wissen möchtest, welche Schlüssel in einem Objekt vorhanden sind.

    Pro:

    • Eine einfache und direkte Methode, um die Schlüssel eines Objekts zu erhalten.
    • Gibt ein Array zurück, das dann mit Array-Methoden wie forEach, map, filter, etc. verarbeitet werden kann.

    Kontra:

    • Gibt nur die eigenen aufzählbaren Eigenschaften eines Objekts zurück, nicht die Eigenschaften, die es von seinem Prototyp erbt.

    An dieser Stelle ist es wichtig das dies Verstanden wird, daher hier noch mal etwas Detalierter.

    In JavaScript können Objekte Eigenschaften von ihren Prototypen erben. Die Methode Object.keys() berücksichtigt jedoch nur die "eigenen" Eigenschaften des Objekts, nicht die Eigenschaften, die es von seinem Prototyp erbt. Das bedeutet, dass nur die Schlüssel zurückgegeben werden, die direkt am Objekt definiert sind, nicht die Schlüssel, die durch Prototypenvererbung verfügbar sind.

    Beispiel:

    1const parentObject = { fruit: 'apple' }; 2const childObject = Object.create(parentObject); 3childObject.color = 'red'; 4 5console.log(Object.keys(childObject)); 6 7// Output: ['color']

    Wir erhalten hier nur den Key "color" nicht aber den Key aus dem parentObject.

    • Wie eben schon gezeigt gibt Object.keys nur die Schlüssel (Keys) zurück, nicht aber die Werte (Values). Dies könnte zb. mit "Array.prototype.map()" gemacht werden

    Beispiel:

    1const fruitColors = { apple: 'red', banana: 'yellow', cherry: 'red' }; 2const keys = Object.keys(fruitColors); 3 4const values = keys.map(key => fruitColors[key]); 5 6console.log(values); // Output: ['red', 'yellow', 'red']

    Schaut folgend aus:

    1const obj = { a: 1, b: 2, c: 3 }; 2const keys = Object.keys(obj); 3 4console.log(keys); 5 6// Output: ['a', 'b', 'c']

    Beispiel:

    Angenommen, du hast ein Objekt, das verschiedene Früchte und ihre Mengen darstellt, und du möchtest alle Früchte auflisten, die du hast.

    1const fruitInventory = { 2 apples: 10, 3 bananas: 20, 4 cherries: 15, 5}; 6 7const fruitNames = Object.keys(fruitInventory); 8 9console.log(fruitNames); 10 11// Output: ['apples', 'bananas', 'cherries']

    Object.values()

    Ist eine nützliche Methode, wenn du eine Liste der Werte eines Objekts benötigst. Quasi das gleiche wie Obejct.keys().

    Pro:

    • Eine einfache und direkte Methode, um die Schlüssel eines Objekts zu erhalten.
    • Gibt ein Array zurück, das dann mit Array-Methoden wie forEach, map, filter, etc. verarbeitet werden kann.

    Kontra:

    Im grunde die selben Probleme wie auch schon bei Object.keys()

    Schaut folgend aus:

    1const obj = { a: 1, b: 2, c: 3 }; 2const values = Object.values(obj); 3 4console.log(values); 5 6// Output: [1, 2, 3]

    Beispiel:

    Angenommen, du hast ein Objekt, das verschiedene Früchte und ihre Mengen darstellt, und du möchtest die Mengen aller Früchte auflisten.

    1const fruitInventory = { 2 apples: 10, 3 bananas: 20, 4 cherries: 15 5}; 6 7const fruitQuantities = Object.values(fruitInventory); 8 9console.log(fruitQuantities); // Output: [10, 20, 15]

    Object.entries()

    ist eine nützliche Methode, wenn du sowohl die Schlüssel als auch die Werte eines Objekts benötigst.

    Pro:

    • Gibt sowohl die Schlüssel als auch die Werte zurück, was nützlich ist, wenn du beide benötigst.
    • Gibt ein Array von Schlüssel-Wert-Paaren zurück, das dann mit Array-Methoden wie forEach, map, filter, etc. verarbeitet werden kann.

    Kontra:

    • Gibt nur die eigenen aufzählbaren Eigenschaften eines Objekts zurück, nicht die Eigenschaften, die es von seinem Prototyp erbt.
    • Da ein neueres Array von Arrays erstellt wird kann es zu Speicherproblemen mit sehr großen Arrays kommen.

    Schaut folgend aus:

    1const obj = { a: 1, b: 2, c: 3 }; 2const entries = Object.entries(obj); 3 4console.log(entries); 5 6// Output: [['a', 1], ['b', 2], ['c', 3]]

    Beispiel:

    1const fruitInventory = { 2 apples: 10, 3 bananas: 20, 4 cherries: 15, 5}; 6 7const fruitEntries = Object.entries(fruitInventory); 8 9console.log(fruitEntries); 10 11// Output: [['apples', 10], ['bananas', 20], ['cherries', 15]] 12``

    Comments: