with(), toSorted(), toReversed(), toSpliced()
Vier neue Array-Methoden liefern eine veraenderte Kopie statt das Original zu mutieren. Das macht sort(), reverse() und splice() in State-getriebenem Code endlich ueberfluessig.
Inhaltsverzeichnis
- 1. Warum mutierende Array-Methoden ein Problem sind
- 2. with(): ein einzelnes Element ersetzen
- 3. toSorted() und toReversed(): sortieren ohne Seiteneffekte
- 4. toSpliced(): einfuegen und entfernen ohne Mutation
- 5. Praktischer Nutzen in React- und Redux-artigem State
- 6. Strukturelles Teilen und Performance-Ueberlegungen
- 7. Typische Fehlerquellen und Migrationsstrategie
- 8. Browser-Unterstuetzung und Polyfill-Strategie
- 9. Vergleichstabelle: mutierend vs. immutabel
- 10. Zusammenfassung
- 11. FAQ
1. Warum mutierende Array-Methoden ein Problem sind
JavaScript-Arrays kennen seit jeher zwei Kategorien von Methoden: solche, die eine neue Kopie zurueckgeben (map(), filter(), slice()), und solche, die das Array an Ort und Stelle veraendern (sort(), reverse(), splice()). Letztere aendern nicht nur den Inhalt des Arrays, sie geben oft sogar dasselbe Array-Objekt als Rueckgabewert zurueck, was den Fehler kaschiert. Wer const sorted = liste.sort() schreibt, hat kein neues Array erzeugt, sondern liste selbst permanent veraendert und lediglich eine zweite Referenz darauf angelegt.
In simplen Skripten faellt das selten auf. Sobald ein Array aber an mehreren Stellen referenziert wird, etwa als Teil eines React-States, eines Redux-Stores oder als Funktionsparameter, der eigentlich nur gelesen werden sollte, wird die Mutation zur Fehlerquelle. Ein Component-State, der sich unter der Hand aendert, ohne dass ein Re-Render ausgeloest wird, ist einer der klassischsten React-Bugs ueberhaupt. Die neuen immutablen Geschwistermethoden loesen dieses Problem an der Wurzel, indem sie exakt dieselbe Funktionalitaet bieten, aber grundsaetzlich eine neue Kopie zurueckgeben.
2. with(): ein einzelnes Element ersetzen
Array.prototype.with(index, value) ersetzt das Element an Position index und gibt ein komplett neues Array zurueck, das Original bleibt unangetastet. Vorher war dafuer meist ein Umweg noetig: entweder array.slice() gefolgt von direkter Indexzuweisung, oder eine Kombination aus Spread-Operator und Template-Trickserei. Beides ist umstaendlich und fehleranfaellig, insbesondere bei negativen Indizes, die with() uebrigens ebenfalls unterstuetzt, genau wie at().
Der praktische Nutzen zeigt sich sofort in typischen UI-Szenarien: eine Todo-Liste, bei der ein einzelner Eintrag als erledigt markiert wird, oder ein Formular-Array, bei dem ein Feld korrigiert werden muss. Statt das ganze Array manuell zu kopieren und den Index von Hand zu setzen, reicht ein einziger, lesbarer Aufruf. Das reduziert nicht nur Codezeilen, sondern auch die Wahrscheinlichkeit, versehentlich doch das Original zu mutieren.
const todos = ["Einkaufen", "Waesche", "Putzen"];
// Alt: manuelle Kopie noetig
const updatedOld = [...todos];
updatedOld[1] = "Waesche (erledigt)";
// Neu: with() liefert direkt eine neue, veraenderte Kopie
const updated = todos.with(1, "Waesche (erledigt)");
console.log(todos); // ["Einkaufen", "Waesche", "Putzen"] -- unveraendert
console.log(updated); // ["Einkaufen", "Waesche (erledigt)", "Putzen"]
// Funktioniert auch mit negativen Indizes wie at()
const lastReplaced = todos.with(-1, "Kochen");
console.log(lastReplaced); // ["Einkaufen", "Waesche", "Kochen"]
3. toSorted() und toReversed(): sortieren ohne Seiteneffekte
toSorted() und toReversed() verhalten sich exakt wie ihre mutierenden Vorbilder sort() und reverse(), inklusive identischer Comparator-Funktion bei toSorted(), geben aber jeweils eine neue Array-Instanz zurueck. Das Original bleibt in seiner urspruenglichen Reihenfolge erhalten. Wer schon einmal den Bug erlebt hat, dass eine an mehreren Stellen im Code gemeinsam genutzte Liste ploetzlich in falscher Reihenfolge angezeigt wurde, weil irgendwo sort() ohne Kopie aufgerufen wurde, weiss, wie subtil dieser Fehler sein kann.
Besonders in Komponenten, die Daten nur zur Anzeige sortieren, ohne die zugrunde liegende Datenquelle zu veraendern, ist toSorted() die richtige Wahl. Ein Sortier-Dropdown in einer Produktliste etwa soll die Ansicht umsortieren, nicht aber die Reihenfolge im eigentlichen Datenmodell durcheinanderbringen, das vielleicht an anderer Stelle noch die urspruengliche Reihenfolge braucht, etwa fuer einen Undo-Mechanismus oder eine zweite Ansicht.
const preise = [42, 7, 19, 3, 88];
const sortiert = preise.toSorted((a, b) => a - b);
const umgekehrt = preise.toReversed();
console.log(preise); // [42, 7, 19, 3, 88] -- unveraendert
console.log(sortiert); // [3, 7, 19, 42, 88]
console.log(umgekehrt); // [88, 3, 19, 7, 42]
// Praktisch in einer React-Reducer-Funktion:
function sortReducer(state, action) {
switch (action.type) {
case "SORT_ASC":
return { ...state, items: state.items.toSorted((a, b) => a.value - b.value) };
default:
return state;
}
}
4. toSpliced(): einfuegen und entfernen ohne Mutation
splice() ist wohl die vielseitigste, aber auch gefaehrlichste Array-Methode: sie kann gleichzeitig Elemente entfernen, einfuegen und ersetzen, mutiert dabei aber immer das Array und gibt zur Verwirrung noch die entfernten Elemente zurueck statt des veraenderten Arrays. toSpliced() uebernimmt exakt dieselbe Signatur, Start-Index, Anzahl zu loeschender Elemente, einzufuegende Elemente, gibt aber die vollstaendige neue Array-Kopie zurueck und laesst das Original in Ruhe.
Damit lassen sich komplexe Listenoperationen wie das Einfuegen eines neuen Eintrags an bestimmter Position oder das Entfernen eines Bereichs in einer einzigen, deklarativen Zeile ausdruecken, ganz ohne Spread-Gymnastik mit slice(0, i) und slice(i + 1). Gerade in Redux-Reducern oder Zustand-Stores, wo jede Mutation des States gegen die Grundregeln verstoesst, ist toSpliced() ein direkter Ersatz fuer fehleranfaellige manuelle Spread-Kombinationen.
const warenkorb = ["Apfel", "Birne", "Kirsche", "Traube"];
// Ein Element an Position 2 einfuegen, nichts loeschen
const eingefuegt = warenkorb.toSpliced(2, 0, "Pflaume");
console.log(eingefuegt); // ["Apfel", "Birne", "Pflaume", "Kirsche", "Traube"]
// Zwei Elemente ab Position 1 entfernen
const entfernt = warenkorb.toSpliced(1, 2);
console.log(entfernt); // ["Apfel", "Traube"]
console.log(warenkorb); // ["Apfel", "Birne", "Kirsche", "Traube"] -- unveraendert
5. Praktischer Nutzen in React- und Redux-artigem State
React und Redux basieren auf dem Prinzip, dass State-Aenderungen immer neue Objektreferenzen erzeugen muessen, damit Change-Detection und Re-Renders zuverlaessig funktionieren. Vor den neuen Methoden war der uebliche Workaround, mutierende Array-Methoden auf einer zuvor per Spread erzeugten Kopie aufzurufen: [...state.items].sort(...). Das funktioniert, ist aber ein zusaetzlicher gedanklicher Schritt, den man bei jedem einzelnen Update erneut korrekt ausfuehren muss, und ein vergessenes Spread ist ein klassischer, schwer zu findender Bug.
Mit toSorted(), toReversed(), with() und toSpliced() entfaellt dieser Zwischenschritt vollstaendig, weil die Immutability direkt in der Methode eingebaut ist. Das reduziert nicht nur Tippfehlerquellen, sondern macht Reducer-Funktionen auch besser lesbar, weil die Absicht direkt aus dem Methodennamen hervorgeht: Ein toSorted()-Aufruf signalisiert sofort, dass hier bewusst eine sortierte Kopie erzeugt wird, ohne dass man erst pruefen muss, ob ein Spread davor steht.
6. Strukturelles Teilen und Performance-Ueberlegungen
Ein haeufiges Missverstaendnis ist, dass immutable Operationen automatisch teuer seien, weil ja immer eine Kopie erzeugt wird. Bei den neuen Array-Methoden stimmt das nur teilweise: Es wird tatsaechlich ein neues Array mit neuen internen Referenzen angelegt, die enthaltenen Objekte selbst werden aber nicht tief kopiert, sondern nur ihre Referenzen uebernommen. Ein Array aus tausend Objekten zu sortieren kostet also nicht das Kopieren von tausend Objekten, sondern lediglich das Anlegen eines neuen Arrays mit denselben tausend Referenzen in neuer Reihenfolge.
Fuer sehr grosse Arrays, die bei jedem einzelnen State-Update komplett neu angelegt werden, kann die Kopie dennoch messbar ins Gewicht fallen, insbesondere bei Listen mit zehntausenden Eintraegen, die mehrfach pro Sekunde aktualisiert werden. In solchen Faellen lohnt sich der Blick auf spezialisierte Persistent-Data-Structure-Bibliotheken mit strukturellem Teilen auf Baumebene. Fuer die allermeisten UI-Anwendungsfaelle, Formulare, Listen mit wenigen hundert Eintraegen, Sortier-Dropdowns, ist der Overhead der neuen Methoden jedoch vernachlaessigbar und der Lesbarkeitsgewinn ueberwiegt deutlich.
7. Typische Fehlerquellen und Migrationsstrategie
Ein subtiler Stolperstein: toSorted() mit Comparator-Funktion sortiert exakt wie sort(), das heisst Zahlen werden ohne Comparator standardmaessig lexikografisch als Strings verglichen. [10, 2, 33].toSorted() liefert also [10, 2, 33] nicht numerisch aufsteigend, sondern lexikografisch, was bei numerischen Arrays oft nicht das gewuenschte Ergebnis ist. Der Comparator (a, b) => a - b bleibt daher weiterhin notwendig, die neuen Methoden aendern nur das Mutationsverhalten, nicht die Sortierlogik selbst.
Fuer die Migration bestehender Codebasen empfiehlt sich ein schrittweises Vorgehen: Zuerst alle Stellen identifizieren, an denen sort(), reverse() oder splice() auf State-Objekten aufgerufen werden, typischerweise per Codesuche oder ESLint-Regel wie no-param-reassign in Kombination mit einer eigenen Regel gegen mutierende Methoden auf Props oder State. Anschliessend koennen diese Stellen direkt durch die immutablen Gegenstuecke ersetzt werden, ohne dass sich die uebrige Logik aendern muss, da Parameter und Rueckgabewert bis auf die Mutation identisch funktionieren.
8. Browser-Unterstuetzung und Polyfill-Strategie
Die vier Methoden sind Teil des ES2023-Standards und seit Ende 2023 in allen aktuellen Versionen von Chrome, Firefox, Safari und Node.js ab Version 20 verfuegbar. Fuer Projekte, die aeltere Browser oder LTS-Node-Versionen unterstuetzen muessen, gibt es das core-js-Polyfill-Paket, das die Methoden transparent nachruestet, ohne dass Anwendungscode angepasst werden muss. Ein Feature-Check per Array.prototype.toSorted === undefined vor dem Laden des Polyfills spart in modernen Umgebungen unnoetigen Overhead.
Fuer TypeScript-Projekte ist zusaetzlich ein aktuelles lib-Target in der tsconfig.json erforderlich, mindestens ES2023, damit der Compiler die neuen Methoden ohne Typfehler kennt. Wer TypeScript mit einem aelteren Target wie ES2020 kompiliert, aber die Runtime trotzdem ES2023-faehig ist, kann die lib-Option unabhaengig vom Kompilierungsziel setzen und so die neuen Typdefinitionen nutzen, ohne die Ausgabesyntax zu veraendern.
9. Vergleichstabelle: mutierend vs. immutabel
Die folgende Tabelle stellt jede mutierende Methode ihrem immutablen Gegenstueck direkt gegenueber und zeigt, welcher Rueckgabewert jeweils zu erwarten ist. Wichtig dabei: Die Parameter sind bei jedem Paar identisch, es aendert sich ausschliesslich das Mutationsverhalten und der Rueckgabewert, sodass eine Migration in der Regel ein reines Suchen-und-Ersetzen des Methodennamens ist.
Als Faustregel gilt: Immer dann, wenn ein Array-Ergebnis in einem State, einem Redux-Store, einem React-Context oder als Rueckgabewert einer reinen Funktion landet, sind die immutablen Varianten die sicherere Wahl. Nur in performancekritischen, rein lokalen Schleifen ohne geteilte Referenzen ist die mutierende Variante weiterhin gerechtfertigt, weil sie eine Array-Allokation einspart.
| Mutierende Methode | Immutable Methode | Rueckgabewert | Original |
|---|---|---|---|
sort() |
toSorted() |
Neues sortiertes Array | Unveraendert |
reverse() |
toReversed() |
Neues umgekehrtes Array | Unveraendert |
splice() |
toSpliced() |
Neues Array mit Aenderung | Unveraendert |
Index-Zuweisung arr[i] = |
with(i, value) |
Neues Array mit ersetztem Element | Unveraendert |
Mironsoft
Moderne Browser-APIs, Performance und wartbares JavaScript
JavaScript, das im echten Browser robust bleibt, nicht nur im Tutorial?
Wir prüfen bestehenden Frontend-Code auf veraltete Patterns, unnötige Bibliotheken und Performance-Fallen und ersetzen sie durch moderne, native Browser-APIs, die weniger Bundle-Gewicht und weniger Wartungslast bedeuten.
Code-Review
Veraltete Patterns, unnötige Dependencies und Memory Leaks systematisch aufspüren.
Performance-Optimierung
Bundle-Größe, Ladezeit und Runtime-Performance mit modernen APIs verbessern.
Modernisierung
Native Browser-APIs statt schwerer Bibliotheken gezielt einführen.
10. Zusammenfassung
Immutable Array-Methoden auf einen Blick
with()
Ersetzt ein Element per Index, liefert eine neue Array-Kopie, unterstuetzt negative Indizes wie at().
toSorted() / toReversed()
Sortieren bzw. umkehren ohne das Original zu veraendern, identische Comparator-Signatur wie sort().
toSpliced()
Einfuegen, entfernen und ersetzen in einem Aufruf, liefert das neue Array statt der entfernten Elemente.
Einsatzgebiet
React-State, Redux-Reducer und jede Stelle, an der geteilte Array-Referenzen vor versehentlicher Mutation geschuetzt werden muessen.