Startseite / Artikel / Moderne JavaScript-Array-Methoden, die gängige Standardmuster ersetzen

Moderne JavaScript-Array-Methoden, die gängige Standardmuster ersetzen

Erfahren Sie, wie neuere JavaScript-Array-Methoden wie groupBy, toSorted, with und findLast umständliche Workarounds mit reduce, spread und reverse ersetzen.

1432 Wörter

Daten gruppieren ohne Bibliothek: Object.groupBy() und Map.groupBy()

Jahrelang bedeutete das Aufteilen einer Array-Liste in Kategorien auf der Grundlage einer gemeinsamen Eigenschaft, entweder einen eigenen reduce()-Akkumulator zu schreiben oder nur für diese eine Aufgabe lodash.groupBy zu verwenden.

Die ECMAScript-Spezifikation bietet nun native Gruppierungsfunktionen über Object.groupBy() und Map.groupBy().

Welches Problem dies löst

Wenn man eine flache Sammlung von Datensätzen aus einer API abruft – Transaktionen, Aufgaben, Benutzerdaten usw. – muss man diese oft in Gruppen einteilen, bevor man sie anzeigt oder darauf Geschäftslogik ausführt.

Vorher: Manuelle Verarbeitung mit reduce()

const inventory = [
  { name: 'Apples', type: 'fruit', quantity: 10 },
  { name: 'Bananas', type: 'fruit', quantity: 0 },
  { name: 'Carrots', type: 'vegetable', quantity: 14 },
  { name: 'Broccoli', type: 'vegetable', quantity: 5 }
];

const groupedByReduce = inventory.reduce((acc, item) => {
  const key = item.type;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(item);
  return acc;
}, {});

Nachher: klare, speziell dafür konzipierte Aufrufe zu Object.groupBy

const inventory = [
  { name: 'Apples', type: 'fruit', quantity: 10 },
  { name: 'Bananas', type: 'fruit', quantity: 0 },
  { name: 'Carrots', type: 'vegetable', quantity: 14 },
  { name: 'Broccoli', type: 'vegetable', quantity: 5 }
];

const grouped = Object.groupBy(inventory, item => item.type);

/*
Output:
{
  fruit: [
    { name: 'Apples', type: 'fruit', quantity: 10 },
    { name: 'Bananas', type: 'fruit', quantity: 0 }
  ],
  vegetable: [
    { name: 'Carrots', type: 'vegetable', quantity: 14 },
    { name: 'Broccoli', type: 'vegetable', quantity: 5 }
  ]
}
*/

Anstelle dessen Map.groupBy() verwenden

Falls Ihre Gruppierungsschlüssel nicht einfach nur eine Zeichenkette oder ein Symbol sind, sondern ein komplexeres Objekt oder eine dynamische Referenz, eignet sich Map.groupBy() besser:

const vipTier = { tier: 'VIP' };
const standardTier = { tier: 'Standard' };

const customers = [
  { name: 'Alice', plan: vipTier },
  { name: 'Bob', plan: standardTier },
  { name: 'Charlie', plan: vipTier }
];

const groupedByPlan = Map.groupBy(customers, user => user.plan);

console.log(groupedByPlan.get(vipTier));
// Returns Alice and Charlie's records directly keyed by reference

Unveränderliche Transformationen, denen man vertrauen kann: toSorted(), toReversed(), toSpliced()

Ein klassisches Problem in JavaScript besteht darin, dass die bekannten Methoden .sort(), .reverse() und .splice() alle das Array direkt an Ort und Stelle überschreiben, anstatt etwas Neues zurückzugeben.

In Frameworks, die auf vorhersehbarem Zustand basieren – React, Redux, Vue und ähnliche – führt die direkte Änderung eines Arrays in der Regel zu subtilen Fehlern sowie einer stillschweigend aus dem Takt geratenen Darstellung.

Welches Problem dies löst

Um diesen Fallen aus dem Weg zu gehen, schrieben Entwickler früher zunächst defensive Kopien, wie zum Beispiel [...array].sort() oder array.slice().reverse(). Die neue Methodefamilie „Array durch Kopie ändern“ beseitigt diesen gesamten Boilerplate völlig.

Vergleich der beiden Ansätze

// --- The Old Mutating Way ---
const scores = [88, 92, 79, 95];
const sortedMutated = scores.sort((a, b) => a - b);

console.log(scores); // [79, 88, 92, 95] -> Original data was destroyed!

// --- The Modern Non-Mutating Way ---
const originalScores = [88, 92, 79, 95];
const cleanSorted = originalScores.toSorted((a, b) => a - b);

console.log(originalScores); // [88, 92, 79, 95] -> Unchanged
console.log(cleanSorted);    // [79, 88, 92, 95] -> Fresh array

Splice() durch toSpliced() ersetzen

toSpliced() ermöglicht es, Einträge an jeder Position zu entfernen, einzufügen oder zu ersetzen, ohne das ursprüngliche Array zu verändern:

const tabs = ['Home', 'About', 'Pricing', 'Contact'];

// Remove 'Pricing' (index 2) and insert 'Services' & 'Blog'
const updatedTabs = tabs.toSpliced(2, 1, 'Services', 'Blog');

console.log(tabs);        // ['Home', 'About', 'Pricing', 'Contact']
console.log(updatedTabs); // ['Home', 'About', 'Services', 'Blog', 'Contact']

Unveränderlich ein Element aktualisieren: with()

Nehmen wir an, Sie möchten den Wert an einer bestimmten Indexposition ändern, ohne das Quellarray zu berühren. Das übliche Muster besteht darin, das Array in eine Kopie zu übertragen, den Index zu überschreiben und das Ergebnis zurückzugeben. with() fasst all das in einer klaren Ausdrucksform zusammen.

Welches Problem dies löst

Bei Aktualisierungen von Arrays muss oft ein einzelnes Element anhand seiner Position ersetzt werden, wobei das umgebende Array unverändert bleiben muss.

Vergleich der beiden Ansätze

const months = ['Jan', 'Mar', 'Mar', 'Apr'];

// Old way: Spread and mutate copy
const fixedMonthsOld = [...months];
fixedMonthsOld[1] = 'Feb';

// Modern way: array.with(index, value)
const fixedMonthsNew = months.with(1, 'Feb');

console.log(months);         // ['Jan', 'Mar', 'Mar', 'Apr']
console.log(fixedMonthsNew);  // ['Jan', 'Feb', 'Mar', 'Apr']

with() unterstützt ebenfalls negative Indizes, sodass das Ersetzen eines Elements von hinten genauso einfach ist:

const tags = ['v1.0.0', 'v1.1.0', 'v1.2.0-beta'];
const stableTags = tags.with(-1, 'v1.2.0');

console.log(stableTags); // ['v1.0.0', 'v1.1.0', 'v1.2.0']

Effizientes Suchen rückwärts: findLast() und findLastIndex()

Das Finden eines Elements mit .find() funktioniert gut, wenn man von vorne beginnt, doch es startet immer bei Index 0. Wenn man eigentlich das letzte Vorkommen sucht, war die übliche Lösung, das Array zunächst umzukehren – ein Ansatz, der Zeit verschwendet und das Risiko birgt, Daten zu verändern, die man eigentlich nicht anfassen wollte.

Welches Problem dies löst

Daten wie Protokolleinträge, Historie der Rückgänge, Aktivitätsfeeds und Transaktionsaufzeichnungen sind von Natur aus nach Zeit geordnet, und man möchte oft den neuesten Eintrag, der einer bestimmten Bedingung entspricht.

Vergleich der beiden Ansätze

const logs = [
  { id: 1, action: 'LOGIN', timestamp: 1000 },
  { id: 2, action: 'UPLOAD', timestamp: 1010 },
  { id: 3, action: 'ERROR', timestamp: 1020 },
  { id: 4, action: 'UPLOAD', timestamp: 1030 },
  { id: 5, action: 'LOGOUT', timestamp: 1040 }
];

// Old way: Reversing creates overhead and array allocation
const lastUploadOld = [...logs].reverse().find(log => log.action === 'UPLOAD');

// Modern way: findLast iterates backwards directly
const lastUpload = logs.findLast(log => log.action === 'UPLOAD');
const lastUploadIdx = logs.findLastIndex(log => log.action === 'UPLOAD');

console.log(lastUpload);    // { id: 4, action: 'UPLOAD', timestamp: 1030 }
console.log(lastUploadIdx); // 3

Weil diese Methode von hinten nach vorne durchsucht und sofort stoppt, sobald ein Passender gefunden wird, läuft sie in O(k) Zeit ab, wobei k die Anzahl der Schritte bis zum Passenden ist – es ist nicht notwendig, zuerst den gesamten Array zu kopieren oder umzukehren.

5. Map und Filter zusammen verwenden: flatMap()

Es ist üblich, .map() mit .filter() oder .flat() zu verketten, wenn jedes Quelleteil in null, ein oder mehrere Ergebnisse umgewandelt werden kann. flatMap() kürzt diese Kette auf einen einzigen Durchlauf ab.

Was es löst: Es vermeidet das Erstellen von temporären Zwischentabellen, wenn man verschachtelte Daten umformen oder in derselben Schritt Filter anwenden und transformieren muss.

Beispiel: Flachlegen einer verschachtelten Liste von Beziehungen

const authors = [
  { name: 'Author A', books: ['Book 1', 'Book 2'] },
  { name: 'Author B', books: ['Book 3'] },
  { name: 'Author C', books: [] }
];

// Map + Flat produces 2 array allocations
const booksNested = authors.map(a => a.books).flat();

// flatMap runs the map and flattens depth-1 in one pass
const allBooks = authors.flatMap(a => a.books);
console.log(allBooks); // ['Book 1', 'Book 2', 'Book 3']

Beispiel: Transformation und Filterung zusammen

Man kann ein Element durch Rückgabe einer leeren Array entfernen und es durch Rückgabe eines eindimensionalen Arrays beibehalten:

const rawInputs = ['42', 'invalid', '100', 'undefined', '256'];

const validNumbers = rawInputs.flatMap(input => {
  const num = parseInt(input, 10);
  return Number.isNaN(num) ? [] : [num];
});

console.log(validNumbers); // [42, 100, 256]

6. Saubererer Zugriff auf Elemente: at()

Der Zugriff auf das letzte Element eines Arrays bedeutete traditionell die Schreibweise array[array.length - 1], was unpraktisch ist und leicht zu Fehlern führt.

at() übernimmt die Konvention negativer Indizes, wie sie aus Python bekannt ist, und ermöglicht es, sowohl bei Arrays als auch bei Zeichenketten rückwärts zu zählen.

Vorher und nachher

const queue = ['Alice', 'Bob', 'Charlie', 'Dana'];

// Old way
const lastPersonOld = queue[queue.length - 1];
const secondLastOld = queue[queue.length - 2];

// Modern way
const lastPerson = queue.at(-1);    // 'Dana'
const secondLast = queue.at(-2);    // 'Charlie'

Das Ergebnis ist kürzer, weniger anfällig für Fehler durch „off-by-one“-Fehler in berechneten Ausdrücken, und es funktioniert gut mit aufgereihten Methodenaufrufen, da man das Array nicht erst in einer temporären Variablen speichern muss.

Wann man darauf zurückgreifen sollte

Modernisierungs-Checkliste

  • Überprüfen Sie, wie Sie den Zustand verwalten: Ersetzen Sie .sort() und .reverse() durch .toSorted() und .toReversed(), um Fehler durch stille Mutationen zu vermeiden.
  • Beseitigen Sie überflüssige Reducer-Codeblöcke: Verwenden Sie stattdessen Object.groupBy() anstelle von manuell erstellten Akkumulatorobjekten.
  • Erhöhen Sie die Suchgeschwindigkeit: ersetzen Sie Muster mit erstem Umkehren und anschließendem Suchen durch findLast() für zeitlich geordnete Daten.
  • Einfache Syntax im Alltag: Verwenden Sie .at(-1) und .with(index, value) anstelle manueller Längenberechnungen und schützender Array-Kopien.
  • Zusätzliche Literatur