Der ultimative Leitfaden für JavaScript-Arrays und -Methoden
November 15, 2016
Arrays gehören zu den wichtigsten und am häufigsten verwendeten Datenstrukturen in JavaScript. Die moderne ECMAScript-Spezifikation bietet eine Vielzahl von eingebauten Methoden, die es ermöglichen, Arrays auf deklarative und funktionale Weise zu manipulieren.
Dieser Leitfaden deckt alles ab, von grundlegenden Iterationen über komplexe Transformationen mit .reduce() bis hin zu tiefergehenden Performance-Analysen.
1. Iterieren: .forEach() vs. .map()
Sowohl .forEach() als auch .map() durchlaufen jedes Element eines Arrays, dienen jedoch völlig unterschiedlichen Zwecken.
.forEach() (Ereignisbasiert mit Seiteneffekten)
.forEach() führt eine übergebene Funktion für jedes Element aus. Sie gibt keinen Wert zurück (undefined) und beendet somit jede Methodenkette. Ihr Hauptzweck ist die Ausführung von Operationen mit Seiteneffekten (Side Effects), wie z.B. das Schreiben in eine Datenbank oder das Manipulieren des DOMs.
const users = ['Alice', 'Bob', 'Charlie'];
// Modifiziert das ursprüngliche Array oder führt Seiteneffekte aus
users.forEach(user => {
console.log(`Benutzer: ${user}`);
});
.map() (Funktionale Transformation)
.map() transformiert jedes Element eines Arrays und gibt ein neues Array derselben Größe zurück. Das ursprüngliche Array bleibt unverändert (Immutability). Dies ist das bevorzugte Werkzeug in der funktionalen Programmierung, da es Seiteneffekte vermeidet.
const numbers = [1, 2, 3];
// Erstellt ein neues Array mit verdoppelten Werten
const doubled = numbers.map(num => num * 2); // [2, 4, 6]
Das Problem mit der Verkettung (Chaining)
Da .forEach() keinen Rückgabewert liefert, können danach keine weiteren Methoden aufgerufen werden. .map() hingegen erlaubt eine flüssige Verkettung.
const arr = [1, 3, 2];
// FUNKTIONIERT: .map() gibt ein Array zurück, das sortiert werden kann
const result = arr.map(x => x * 2).sort(); // [2, 4, 6]
// FEHLER: .forEach() gibt undefined zurück, 'sort' ist darauf nicht verfügbar
arr.forEach(x => x * 2).sort(); // TypeError: Cannot read property 'sort' of undefined
2. Filtern mit .filter()
.filter() durchläuft ein Array und gibt ein neues Array zurück, das nur die Elemente enthält, für die die übergebene Callback-Funktion true (Wahr) zurückgibt.
const scores = [45, 80, 92, 30, 75];
// Filtert alle Punktzahlen unter 50 heraus
const passingScores = scores.filter(score => score >= 50); // [80, 92, 75]
3. Sortieren mit .sort()
Die .sort()-Methode sortiert die Elemente eines Arrays in-place (das ursprüngliche Array wird modifiziert) und gibt das sortierte Array zurück.
Wichtig: Ohne eine übergebene Vergleichsfunktion konvertiert .sort() alle Elemente in Strings und sortiert sie alphabetisch (Unicode-Reihenfolge). Dies führt bei Zahlen zu unerwarteten Ergebnissen.
const numbers = [1, 12, 21, 2];
// Unerwartetes Ergebnis ohne Callback:
numbers.sort(); // [1, 12, 2, 21] (da '12' vor '2' sortiert wird)
// Korrektes numerisches Sortieren mit einer Vergleichsfunktion:
// a - b < 0: a wird vor b sortiert (aufsteigend)
// a - b > 0: b wird vor a sortiert (absteigend)
numbers.sort((a, b) => a - b); // [1, 2, 12, 21]
4. Transformieren und Zusammenfassen mit .reduce()
Die .reduce()-Methode reduziert ein ganzes Array auf einen einzelnen Wert (dieser Wert kann eine Zahl, ein String, ein Array oder ein komplexes Objekt sein). Sie benötigt einen Reduzierer (Reducer) und einen optionalen Initialwert für den Akkumulator.
Beispiel 1: Summe aller Zahlen berechnen
const data = [10, 20, 30, 40];
const sum = data.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0); // 0 ist der Initialwert für den Akkumulator
console.log(sum); // 100
Beispiel 2: Stimmen auszählen (Tally)
Dieses Muster konvertiert ein flaches Array in ein Objekt, das die Häufigkeit der Einträge zählt.
const votes = ["react", "react", "vue", "angular", "react", "vue"];
const tally = votes.reduce((accumulator, vote) => {
accumulator[vote] = (accumulator[vote] || 0) + 1;
return accumulator;
}, {}); // Initialwert ist ein leeres Objekt {}
console.log(tally); // { react: 3, vue: 2, angular: 1 }
Beispiel 3: API-Arrays in Key-Value-Abfrageobjekte konvertieren
Viele APIs (wie die TMDB Movie API) geben Genres als Array von Objekten zurück. Um den Namen eines Genres anhand seiner ID schnell zu finden, ist eine Konvertierung in ein flaches Objekt ideal, um O(1)-Abfragen zu ermöglichen.
Eingangsdaten (Array von Objekten):
const genresArray = [
{ id: 28, name: "Action" },
{ id: 12, name: "Adventure" },
{ id: 16, name: "Animation" }
];
Konvertierung mit .reduce():
const genresObject = genresArray.reduce((acc, genre) => {
acc[genre.id] = genre.name;
return acc;
}, {});
console.log(genresObject);
// Ausgabe: { 28: "Action", 12: "Adventure", 16: "Animation" }
// Extrem schnelle Abfrage ohne Schleife:
console.log(genresObject[12]); // "Adventure"
5. Performance-Analyse: .reduce() vs. Chaining (.filter().map())
Es wird oft behauptet, dass .reduce() für die Verarbeitung großer Datenmengen performanter ist als die Verkettung (Chaining) von .filter() und .map(), da das Array nur einmal durchlaufen wird. Ein empirischer Test mit 1 Million Einträgen in modernen JavaScript-Engines (V8) zeigt jedoch ein anderes Bild.
Der Benchmark-Test
Wir generieren ein Array mit einer Million Zahlen, filtern alle geraden Zahlen heraus und verdoppeln diese.
const bigData = Array.from({ length: 1000000 }, (_, i) => i);
// Methode A: Chaining (Zwei Durchläufe)
console.time("Chaining");
const chainedResult = bigData
.filter(val => val % 2 === 0)
.map(val => val * 2);
console.timeEnd("Chaining"); // Typisches Ergebnis: ca. 35ms
// Methode B: Reduce (Ein Durchlauf, Array-Mutation)
console.time("Reduce");
const reducedResult = bigData.reduce((acc, val) => {
if (val % 2 === 0) {
acc.push(val * 2);
}
return acc;
}, []);
console.timeEnd("Reduce"); // Typisches Ergebnis: ca. 50ms
Warum ist Chaining oft schneller?
Obwohl das Chaining das Array zweimal durchläuft, ist es in modernen Engines häufig schneller als .reduce(). Die Gründe hierfür sind:
- V8-Optimierungen: Die Engines sind extrem hochgradig auf einfache Operationen wie
.mapund.filteroptimiert. - Array-Wachstum bei Reduce: Innerhalb des Reduzierers wird bei jedem passenden Element
acc.push()aufgerufen. Das dynamische Vergrößern des Akkumulator-Arrays erfordert internen Speicher-Overhead. - Lesbarkeit vor spekulativer Optimierung: Chaining ist in der Regel lesbarer und sollte aus architektonischer Sicht bevorzugt werden. Nutzen Sie
.reduce()primär dann, wenn sich die Datenstruktur grundlegend ändert (z.B. von Array zu Objekt), nicht aus reinen Performance-Gründen.
6. Weitere wichtige Hilfsmethoden
.concat(): Verschmilzt zwei oder mehr Arrays, ohne die bestehenden zu verändern (gibt ein neues Array zurück).codeconst combined = arr1.concat(arr2);.slice(begin, end): Extrahiert einen flachen Teil eines Arrays in ein neues Array (vonbeginbis exklusiveend).codeconst slice = arr.slice(1, 3);.join(separator): Verbindet alle Elemente eines Arrays zu einem String, getrennt durch den angegebenen Separator.codeconst csv = ['A', 'B', 'C'].join(','); // "A,B,C".indexOf(element): Sucht nach einem Element und gibt dessen Index zurück. Gibt-1zurück, wenn das Element nicht existiert.codeconst hasElement = arr.indexOf('X') !== -1;- Duplikate entfernen mit
Set: Ein Set speichert nur einzigartige Werte. In Kombination mit dem Spread-Operator (...) ist dies die eleganteste Methode, um Duplikate aus einem Array zu entfernen.codeconst duplicates = [1, 2, 2, 3, 1, 4]; const unique = [...new Set(duplicates)]; // [1, 2, 3, 4]
Fazit
Die Beherrschung von Array-Methoden ist eine Kernkompetenz im modernen JavaScript. Die Bevorzugung von transformierenden, seiteneffektfreien Methoden wie .map() und .filter() führt zu saubererem und wartbarerem Code. Während .reduce() ein mächtiges Schweizer Taschenmesser für Strukturänderungen ist, zeigt die Performance-Analyse, dass einfache, lesbare Verkettungen in der Praxis oft die beste Wahl sind.