Abschnitt 9 · Lektion 4
Suchen, prüfen, ersetzen
Bis hierhin hast du Muster geschrieben und mit test geprüft. Jetzt kommen die drei anderen Methoden dazu, die eines benutzen, und eine Überraschung, die man einmal erlebt haben muss.
Vier Methoden, drei Rückgabewerte
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const text = "3 Kurse, 47 Stunden";
// test steht am Muster und liefert wahr oder falsch.
console.log("test:", /\d+/.test(text));
// match steht am Text und liefert den Treffer als Liste.
console.log("match:", text.match(/\d+/));
console.log("nur der Treffer:", text.match(/\d+/)[0]);
// Mit g liefert match alle Treffer, aber ohne Gruppen.
console.log("match mit g:", text.match(/\d+/g));
// matchAll liefert alle Treffer je als volle Liste mit Gruppen.
// Es gibt einen Iterator zurück, den man ausbreitet.
const alle = [...text.matchAll(/(?<zahl>\d+) (?<wort>\w+)/g)];
console.log("wie viele:", alle.length);
console.log("erste Gruppe:", alle[0].groups);
console.log("zweite Gruppe:", alle[1].groups); test steht am Muster und liefert true oder false. Das kennst du.
match steht am Text und liefert den Treffer. Ohne g bekommst du eine Liste wie in Lektion 9.3: an Stelle 0 der ganze Treffer, dahinter die Gruppen, dazu groups bei benannten Gruppen.
Mit g ändert match sein Verhalten: es liefert dann alle Treffer als schlichte Textliste und lässt die Gruppen weg. Das ist ein häufiger Stolperstein, denn man erwartet mehr statt weniger. Für „hol mir alle Zahlen” ist es genau richtig.
Wenn du alle Treffer mit ihren Gruppen brauchst, nimmst du matchAll. Es setzt g voraus und gibt einen Iterator zurück, den man mit den drei Punkten aus Lektion 7.6 zu einer Liste ausbreitet.
Kein Treffer heißt null
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const ohneZahl = "Kurse und Stunden";
// Kein Treffer heißt null. Nicht undefined, nicht [].
console.log("match:", ohneZahl.match(/\d+/));
console.log("mit g:", ohneZahl.match(/\d+/g));
// Deshalb bricht jede Weiterverarbeitung ohne Absicherung ab.
console.log("Länge von null:", ohneZahl.match(/\d+/g)?.length);
// Zwei übliche Absicherungen, beide aus Abschnitt 7.
console.log("mit ??:", ohneZahl.match(/\d+/g) ?? []);
console.log("Länge:", (ohneZahl.match(/\d+/g) ?? []).length);
// matchAll ist hier freundlicher: kein Treffer heißt
// einfach eine leere Liste.
console.log("matchAll:", [...ohneZahl.matchAll(/\d+/g)].length); match liefert bei keinem Treffer null, nicht undefined und nicht die leere Liste. Das ist eine bewusste Entscheidung von 1995 und heute vor allem eine Fehlerquelle.
Die Folge kennst du aus Lektion 7.4: text.match(...).length bricht mit einem TypeError ab, sobald kein Treffer da ist. Und da ein Testtext meistens einen Treffer hat, fällt das erst in den echten Daten auf. In der Eingabezeile ist es eine Zeile: "abc".match(/\d/) antwortet mit null.
Die Absicherung ist immer dieselbe: ?. für den Zugriff oder ?? [] für einen brauchbaren Ersatz. (text.match(/\d+/g) ?? []).length ist die Zeile, die man sich merkt.
matchAll ist hier freundlicher: ohne Treffer bekommst du einfach eine leere Liste, und eine Schleife darüber tut dann nichts. Das ist einer der Gründe, es zu bevorzugen.
Ersetzen mit dem, was gefunden wurde
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const datum = "2026-08-03";
// Im Ersatztext steht $1 für die erste Gruppe, $2 für die zweite.
console.log("Deutsch:", datum.replace(/^(\d{4})-(\d{2})-(\d{2})$/, "$3.$2.$1"));
// Benannte Gruppen schreibt man dort als $<name>.
const muster = /^(?<jahr>\d{4})-(?<monat>\d{2})-(?<tag>\d{2})$/;
console.log("Benannt:", datum.replace(muster, "$<tag>.$<monat>.$<jahr>"));
// Statt eines Textes darf dort auch eine Funktion stehen.
// Sie bekommt den Treffer und die Gruppen als Argumente.
const laut = "3 Kurse, 47 Stunden".replace(/\d+/g, (treffer) => treffer * 2);
console.log("Verdoppelt:", laut);
// Damit lässt sich rechnen oder von Fall zu Fall entscheiden,
// und genau dafür gibt es die Funktion.
const maskiert = "mario@example.com".replace(
/^(.)([^@]*)(@.*)$/,
(treffer, erstes, mitte, rest) => erstes + "*".repeat(mitte.length) + rest
);
console.log("Maskiert:", maskiert); replace und replaceAll kennst du aus Lektion 8.2, dort mit festen Texten. Mit einem Muster können sie mehr, denn im Ersatztext darfst du auf die Gruppen zugreifen.
$1 steht für die erste Gruppe, $2 für die zweite, und bei benannten Gruppen schreibt man $<name>. Damit wird das Umsortieren eines Datums zu einer einzigen Zeile.
Statt eines Textes darf dort auch eine Funktion stehen. Sie bekommt den ganzen Treffer als erstes Argument und danach die Gruppen, und was sie zurückgibt, wird eingesetzt. Das brauchst du, sobald der Ersatz gerechnet werden muss statt nur zusammengesetzt.
Für replace mit einem Muster gilt weiterhin: ohne g nur das erste Vorkommen. replaceAll verlangt bei einem Muster sogar ausdrücklich das g, sonst wirft es einen TypeError.
Die Falle, die man einmal erlebt haben muss
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> // Ein Muster mit g merkt sich, wo es zuletzt aufgehört hat.
const mitG = /\d/g;
console.log("erster Aufruf:", mitG.test("a1b2"));
console.log("zweiter Aufruf:", mitG.test("a1b2"));
console.log("dritter Aufruf:", mitG.test("a1b2"));
console.log("vierter Aufruf:", mitG.test("a1b2"));
// Der Grund ist eine Eigenschaft am Muster selbst.
console.log("lastIndex:", mitG.lastIndex);
// Ohne g passiert das nicht.
const ohneG = /\d/;
console.log("ohne g:", ohneG.test("a1b2"), ohneG.test("a1b2"), ohneG.test("a1b2"));
// Die Regel: kein g an einem Muster, das du mit test benutzt.
// Und kein Muster mit g in einer Variablen wiederverwenden,
// wenn du nicht genau weißt, warum. Und jetzt das Unangenehmste dieser Lektion. Ein Muster mit g merkt sich beim test seine Position.
Rufst du mitG.test("a1b2") viermal hintereinander mit demselben Text auf, bekommst du true, true, false, true. Der Grund ist eine Eigenschaft lastIndex am Muster selbst: nach einem Treffer steht dort, wo es weitersuchen soll, und beim dritten Aufruf ist der Text zu Ende. Danach beginnt es wieder von vorn.
Diesen Absatz solltest du nicht glauben, sondern nachstellen, und die Eingabezeile der Console ist genau dafür da. Tipp const mitG = /\d/g, dann viermal dieselbe Zeile mitG.test("a1b2"). Vier gleiche Fragen, vier Antworten, und die dritte ist eine andere. Frag zwischendurch mitG.lastIndex, dann siehst du auch, woran es liegt. Kein anderes Werkzeug zeigt das so, denn im Editor müsstest du die Zeile viermal hinschreiben, und dann sieht sie schon nach Absicht aus.
Das ist kein Fehler im Browser, es ist so vorgesehen und für eine Schleife über alle Treffer auch sinnvoll. Aber es macht aus einem Muster einen Wert mit Gedächtnis, und damit rechnet niemand.
Zwei Regeln, die das vollständig vermeiden. Erstens: kein g an einem Muster, das du mit test benutzt. Zweitens: ein Muster mit g nicht in einer Variablen aufheben und mehrfach verwenden, außer du weißt genau, warum. Wer beides einhält, begegnet lastIndex nie wieder.
Zum Mitnehmen
Ein Muster mit g merkt sich beim test seine Position. Der zweite Aufruf mit demselben Text liefert deshalb ein anderes Ergebnis, und das ist eine der unangenehmsten Überraschungen der Sprache.
Jetzt du
Basis Konto, kostenlosZu 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.