RegExp)|)/uregExp.test(str): Gibt es einen Treffer?str.search(regExp): An welchem Index ist der Treffer?regExp.exec(str): Erfassungsgruppenstr.match(regExp): Alle Treffer der Gruppe 0 erhaltenstr.matchAll(regExp): Ein Iterable über alle Trefferobjekte erhalten [ES2020]regExp.exec() vs. str.match() vs. str.matchAll()str.replace() und str.replaceAll()/g und /y und die Eigenschaft .lastIndex (fortgeschritten)/g und /y/g und /y beeinflusst?/g und /y und wie man mit ihnen umgeht.lastIndex: Treffersuche ab einem bestimmten Index starten.lastIndex.global (/g) und .sticky (/y) Verfügbarkeit von Funktionen
Sofern nicht anders angegeben, ist jede Funktion für reguläre Ausdrücke seit ES3 verfügbar.
Die beiden Hauptwege zur Erstellung regulärer Ausdrücke sind:
Literal: statisch kompiliert (beim Laden).
/abc/uiKonstruktor: dynamisch kompiliert (zur Laufzeit).
new RegExp('abc', 'ui')Beide regulären Ausdrücke haben die gleichen zwei Teile:
abc – der eigentliche reguläre Ausdruck.u und i. Flags konfigurieren, wie das Muster interpretiert wird. Zum Beispiel aktiviert i die Groß-/Kleinschreibung-unabhängige Suche. Eine Liste der verfügbaren Flags finden Sie später in diesem Kapitel.Es gibt zwei Varianten des Konstruktors RegExp():
new RegExp(pattern : string, flags = '') [ES3]
Ein neuer regulärer Ausdruck wird wie durch pattern spezifiziert erstellt. Wenn flags fehlt, wird der leere String '' verwendet.
new RegExp(regExp : RegExp, flags = regExp.flags) [ES6]
regExp wird geklont. Wenn flags angegeben wird, bestimmt es die Flags des Klons.
Die zweite Variante ist nützlich zum Klonen regulärer Ausdrücke, optional während der Modifikation. Flags sind unveränderlich und dies ist der einzige Weg, sie zu ändern – zum Beispiel:
function copyAndAddFlags(regExp, flagsToAdd='') {
// The constructor doesn’t allow duplicate flags;
// make sure there aren’t any:
const newFlags = Array.from(
new Set(regExp.flags + flagsToAdd)
).join('');
return new RegExp(regExp, newFlags);
}
assert.equal(/abc/i.flags, 'i');
assert.equal(copyAndAddFlags(/abc/i, 'g').flags, 'gi');Auf der obersten Ebene eines regulären Ausdrucks sind die folgenden Syntaxzeichen besonders. Sie werden durch Voranstellen eines Backslash (\) maskiert.
\ ^ $ . * + ? ( ) [ ] { } |
In regulären Expressions-Literalen müssen wir Schrägstriche maskieren:
> /\//.test('/')
trueIm Argument von new RegExp() müssen wir keine Schrägstriche maskieren:
> new RegExp('/').test('/')
trueAtome sind die grundlegenden Bausteine regulärer Ausdrücke.
^, $ usw.). Musterzeichen passen für sich selbst. Beispiele: A b %. passt für jedes Zeichen. Wir können das Flag /s (dotAll) verwenden, um zu steuern, ob der Punkt Zeilenabschlüsse übereinstimmt oder nicht (weiter unten).\f: Form-Feed (FF)\n: Zeilenvorschub (LF)\r: Wagenrücklauf (CR)\t: Tabulator\v: vertikaler Tabulator\cA (Ctrl-A), …, \cZ (Ctrl-Z)\u00E4/u): \u{1F44D}\d: Ziffern (gleich wie [0-9])\D: Nicht-Ziffern\w: „Wort“-Zeichen (gleich wie [A-Za-z0-9_], bezogen auf Bezeichner in Programmiersprachen)\W: Nicht-Wort-Zeichen\s: Leerzeichen (Leerzeichen, Tabulator, Zeilenabschlüsse usw.)\S: Nicht-Leerzeichen\p{White_Space}, \P{White_Space} usw./u.Im Unicode-Standard hat jedes Zeichen Eigenschaften – Metadaten, die es beschreiben. Eigenschaften spielen eine wichtige Rolle bei der Definition der Natur eines Zeichens. Zitat aus dem Unicode-Standard, Abschn. 3.3, D3:
Die Semantik eines Zeichens wird durch seine Identität, normative Eigenschaften und sein Verhalten bestimmt.
Hier sind einige Beispiele für Eigenschaften:
Name: ein eindeutiger Name, bestehend aus Großbuchstaben, Ziffern, Bindestrichen und Leerzeichen – zum Beispiel:Name = LATIN CAPITAL LETTER A🙂: Name = SLIGHTLY SMILING FACEGeneral_Category: kategorisiert Zeichen – zum Beispiel:General_Category = Lowercase_LetterGeneral_Category = Currency_SymbolWhite_Space: wird zum Markieren unsichtbarer Leerzeichen verwendet, wie Leerzeichen, Tabs und Zeilenumbrüche – zum Beispiel:White_Space = TrueWhite_Space = FalseAge: Version des Unicode-Standards, in der ein Zeichen eingeführt wurde – zum Beispiel: Das Euro-Zeichen € wurde in Version 2.1 des Unicode-Standards hinzugefügt.Age = 2.1Block: ein zusammenhängender Bereich von Codepunkten. Blöcke überlappen sich nicht und ihre Namen sind eindeutig. Zum Beispiel:Block = Basic_Latin (Bereich U+0000..U+007F)🙂: Block = Emoticons (Bereich U+1F600..U+1F64F)Script: eine Sammlung von Zeichen, die von einem oder mehreren Schriftsystemen verwendet werden.Script = GreekScript = CyrillicUnicode-Eigenschafts-Escapes sehen so aus:
\p{prop=value}: passt für alle Zeichen, deren Eigenschaft prop den Wert value hat.\P{prop=value}: passt für alle Zeichen, die keine Eigenschaft prop mit dem Wert value haben.\p{bin_prop}: passt für alle Zeichen, deren binäre Eigenschaft bin_prop True ist.\P{bin_prop}: passt für alle Zeichen, deren binäre Eigenschaft bin_prop False ist.Kommentare
Wir können Unicode-Eigenschafts-Escapes nur verwenden, wenn das Flag /u gesetzt ist. Ohne /u ist \p gleichbedeutend mit p.
Formen (3) und (4) können als Abkürzungen verwendet werden, wenn die Eigenschaft General_Category ist. Zum Beispiel sind die folgenden beiden Escapes äquivalent:
\p{Uppercase_Letter}
\p{General_Category=Uppercase_Letter}Beispiele
Prüfen auf Leerzeichen
> /^\p{White_Space}+$/u.test('\t \n\r')
truePrüfen auf griechische Buchstaben
> /^\p{Script=Greek}+$/u.test('μετά')
trueLöschen aller Buchstaben
> '1π2ü3é4'.replace(/\p{Letter}/ug, '')
'1234'Löschen von Kleinbuchstaben
> 'AbCdEf'.replace(/\p{Lowercase_Letter}/ug, '')
'ACE'Weiterführende Lektüre
Eine Zeichenklasse fasst Klassenbereiche in eckigen Klammern zusammen. Die Klassenbereiche geben eine Menge von Zeichen an:
[«klassenbereiche»] passt für jedes Zeichen in der Menge.[^«klassenbereiche»] passt für jedes Zeichen, das nicht in der Menge ist.Regeln für Klassenbereiche:
Nicht-Syntaxzeichen stehen für sich selbst: [abc]
Nur die folgenden vier Zeichen sind besonders und müssen mit Schrägstrichen maskiert werden:
^ \ - ]
^ muss nur maskiert werden, wenn es zuerst kommt.- muss nicht maskiert werden, wenn es zuerst oder zuletzt kommt.Zeichen-Escapes (\n, \u{1F44D} usw.) haben die übliche Bedeutung.
\b steht für Backspace. An anderer Stelle in einem regulären Ausdruck passt es für Wortgrenzen.Zeichenklassen-Escapes (\d, \p{White_Space} usw.) haben die übliche Bedeutung.
Zeichenbereiche werden über Bindestriche angegeben: [a-z]
(#+)\1, \2 usw.(?<hashes>#+)\k<hashes>(?:#+)Standardmäßig sind alle folgenden Quantifizierer gierig (sie passen auf so viele Zeichen wie möglich):
?: nie oder einmal*: null oder mehr+: eins oder mehr{n}: genau n mal{n,}: mindestens n mal{n,m}: mindestens n mal, höchstens m mal.Um sie geizig zu machen (damit sie so wenige Zeichen wie möglich passen), setzen Sie Fragezeichen (?) dahinter:
> /".*"/.exec('"abc"def"')[0] // greedy
'"abc"def"'
> /".*?"/.exec('"abc"def"')[0] // reluctant
'"abc"'^ passt nur am Anfang des Eingangs$ passt nur am Ende des Eingangs\b passt nur an einer Wortgrenze\B passt nur, wenn nicht an einer WortgrenzePositive Lookahead: (?=«muster») passt, wenn muster das Folgende übereinstimmt.
Beispiel: Folgen von Kleinbuchstaben, denen ein X folgt.
> 'abcX def'.match(/[a-z]+(?=X)/g)
[ 'abc' ]Beachten Sie, dass das X selbst nicht Teil des übereinstimmenden Teilstrings ist.
Negative Lookahead: (?!«muster») passt, wenn muster das Folgende nicht übereinstimmt.
Beispiel: Folgen von Kleinbuchstaben, denen kein X folgt.
> 'abcX def'.match(/[a-z]+(?!X)/g)
[ 'ab', 'def' ]Positive Lookbehind: (?<=«muster») passt, wenn muster dem Vorhergehenden entspricht.
Beispiel: Folgen von Kleinbuchstaben, denen ein X vorausgeht.
> 'Xabc def'.match(/(?<=X)[a-z]+/g)
[ 'abc' ]Negative Lookbehind: (?<!«muster») passt, wenn muster dem Vorhergehenden nicht entspricht.
Beispiel: Folgen von Kleinbuchstaben, denen kein X vorausgeht.
> 'Xabc def'.match(/(?<!X)[a-z]+/g)
[ 'bc', 'def' ]Beispiel: Ersetze „.js“ durch „.html“, aber nicht in „Node.js“.
> 'Node.js: index.js and main.js'.replace(/(?<!Node)\.js/g, '.html')
'Node.js: index.html and main.html'|)Vorsicht: Dieser Operator hat niedrige Priorität. Verwenden Sie bei Bedarf Gruppen:
^aa|zz$ passt für alle Strings, die mit aa und/oder zz beginnen. Beachten Sie, dass | eine geringere Priorität hat als ^ und $.^(aa|zz)$ passt für die beiden Strings 'aa' und 'zz'.^a(a|z)z$ passt für die beiden Strings 'aaz' und 'azz'.| Literal-Flag | Eigenschaftsname | ES | Beschreibung |
|---|---|---|---|
d |
hasIndices |
ES2022 | Schaltet Trefferindizes ein |
g |
global |
ES3 | Mehrfach übereinstimmen |
i |
ignoreCase |
ES3 | Groß-/Kleinschreibung ignorieren |
m |
multiline |
ES3 | ^ und $ passen pro Zeile |
s |
dotAll |
ES2018 | Der Punkt passt auf Zeilenumbrüche |
u |
unicode |
ES6 | Unicode-Modus (empfohlen) |
y |
sticky |
ES6 | Keine Zeichen zwischen Treffern |
Die folgenden Flags für reguläre Ausdrücke sind in JavaScript verfügbar (Tab. 21 bietet eine kompakte Übersicht)
/d (.hasIndices): Einige RegExp-bezogene Methoden geben Trefferobjekte zurück, die beschreiben, wo der reguläre Ausdruck in einem Eingangsstring übereinstimmt. Wenn dieses Flag aktiviert ist, enthält jedes Trefferobjekt Trefferindizes, die uns sagen, wo jede Erfassungsgruppe beginnt und endet. Mehr Informationen: §43.5.1 „Trefferindizes in Trefferobjekten [ES2022]“.
/g (.global) ändert grundlegend, wie die folgenden Methoden funktionieren.
RegExp.prototype.test()RegExp.prototype.exec()String.prototype.match()Wie, wird in §43.7 „Die Flags /g und /y und die Eigenschaft .lastIndex“ erklärt. Kurz gesagt: Ohne /g berücksichtigen die Methoden nur den ersten Treffer eines regulären Ausdrucks in einem Eingangsstring. Mit /g berücksichtigen sie alle Treffer.
/i (.ignoreCase) schaltet die Groß-/Kleinschreibung-unabhängige Suche ein.
> /a/.test('A')
false
> /a/i.test('A')
true/m (.multiline): Wenn dieses Flag aktiviert ist, passt ^ am Anfang jeder Zeile und $ am Ende jeder Zeile. Wenn es deaktiviert ist, passt ^ am Anfang des gesamten Eingangsstrings und $ am Ende des gesamten Eingangsstrings.
> 'a1\na2\na3'.match(/^a./gm)
[ 'a1', 'a2', 'a3' ]
> 'a1\na2\na3'.match(/^a./g)
[ 'a1' ]/u (.unicode): Dieses Flag schaltet den Unicode-Modus für einen regulären Ausdruck ein. Dieser Modus wird im nächsten Unterabschnitt erklärt.
/y (.sticky): Dieses Flag ist hauptsächlich in Verbindung mit /g sinnvoll. Wenn beide eingeschaltet sind, muss jeder Treffer direkt auf den vorherigen folgen (d. h. er muss bei Index .lastIndex des regulären Ausdrucksobjekts beginnen). Daher muss der erste Treffer bei Index 0 erfolgen.
> 'a1a2 a3'.match(/a./gy)
[ 'a1', 'a2' ]
> '_a1a2 a3'.match(/a./gy) // first match must be at index 0
null
> 'a1a2 a3'.match(/a./g)
[ 'a1', 'a2', 'a3' ]
> '_a1a2 a3'.match(/a./g)
[ 'a1', 'a2', 'a3' ]Der Hauptanwendungsfall für /y ist die Tokenisierung (während des Parsens). Mehr Informationen zu diesem Flag: §43.7 „Die Flags /g und /y und die Eigenschaft .lastIndex“.
/s (.dotAll): Standardmäßig passt der Punkt keine Zeilenumbrüche. Mit diesem Flag tut er das.
> /./.test('\n')
false
> /./s.test('\n')
trueWorkaround: Wenn /s nicht unterstützt wird, können wir [^] anstelle eines Punktes verwenden.
> /[^]/.test('\n')
trueBetrachten Sie den folgenden regulären Ausdruck: /“([^”]+)”/udg
In welcher Reihenfolge sollten wir seine Flags auflisten? Zwei Optionen sind:
/dgu/u am grundlegendsten usw.): /ugdDa (2) nicht offensichtlich ist, ist (1) die bessere Wahl. JavaScript verwendet es auch für die RegExp-Eigenschaft .flags.
> /a/ismudgy.flags
'dgimsuy'/uDas Flag /u schaltet einen speziellen Unicode-Modus für reguläre Ausdrücke ein. Dieser Modus ermöglicht mehrere Funktionen:
In Mustern können wir Unicode-Codepunkt-Escapes wie \u{1F42A} verwenden, um Zeichen anzugeben. Code-Einheiten-Escapes wie \u03B1 haben nur einen Bereich von vier Hexadezimalziffern (was der grundlegenden mehrsprachigen Ebene entspricht).
In Mustern können wir Unicode-Eigenschafts-Escapes wie \p{White_Space} verwenden.
Viele Escapes sind nun verboten. Zum Beispiel: \a \- \:
Musterzeichen passen immer für sich selbst.
> /pa-:/.test('pa-:')
trueOhne /u gibt es einige Musterzeichen, die immer noch für sich selbst passen, wenn wir sie mit Backslashes maskieren:
> /\p\a\-\:/.test('pa-:')
trueMit /u:
\p startet einen Unicode-Eigenschafts-Escape.Die atomaren Einheiten für den Abgleich sind Unicode-Zeichen (Codepunkte), nicht JavaScript-Zeichen (Code-Einheiten).
Die folgenden Unterabschnitte erklären den letzten Punkt genauer. Sie verwenden das folgende Unicode-Zeichen, um zu erklären, wann atomare Einheiten Unicode-Zeichen sind und wann JavaScript-Zeichen:
const codePoint = '🙂';
const codeUnits = '\uD83D\uDE42'; // UTF-16
assert.equal(codePoint, codeUnits); // same string!Ich wechsle nur zwischen 🙂 und \uD83D\uDE42, um zu veranschaulichen, wie JavaScript Dinge sieht. Beide sind äquivalent und können in Strings und regulären Ausdrücken austauschbar verwendet werden.
Mit /u werden die beiden Code-Einheiten von 🙂 als einzelnes Zeichen behandelt.
> /^[🙂]$/u.test('🙂')
trueOhne /u wird 🙂 als zwei Zeichen behandelt.
> /^[\uD83D\uDE42]$/.test('\uD83D\uDE42')
false
> /^[\uD83D\uDE42]$/.test('\uDE42')
trueBeachten Sie, dass ^ und $ erfordern, dass der Eingangsstring ein einzelnes Zeichen hat. Deshalb ist das erste Ergebnis false.
.) passt für Unicode-Zeichen, nicht für JavaScript-ZeichenMit /u passt der Punktoperator für Unicode-Zeichen:
> '🙂'.match(/./gu).length
1.match() mit /g gibt ein Array mit allen Treffern eines regulären Ausdrucks zurück.
Ohne /u passt der Punktoperator für JavaScript-Zeichen:
> '\uD83D\uDE80'.match(/./g).length
2Mit /u gilt ein Quantifizierer für das gesamte vorhergehende Unicode-Zeichen:
> /^🙂{3}$/u.test('🙂🙂🙂')
trueOhne /u gilt ein Quantifizierer nur für das vorhergehende JavaScript-Zeichen:
> /^\uD83D\uDE80{3}$/.test('\uD83D\uDE80\uDE80\uDE80')
trueBemerkenswert:
.lastIndex eine echte Instanzeigenschaft. Alle anderen Eigenschaften werden über Getter implementiert..lastIndex die einzige veränderliche Eigenschaft. Alle anderen Eigenschaften sind schreibgeschützt. Wenn wir sie ändern wollen, müssen wir den regulären Ausdruck kopieren (siehe §43.1.2 „Reguläre Ausdrücke klonen und nicht-destruktiv modifizieren“ für Details).Jedes Flag für reguläre Ausdrücke existiert als Eigenschaft mit einem längeren, beschreibenderen Namen:
> /a/i.ignoreCase
true
> /a/.ignoreCase
falseDies ist die vollständige Liste der Flag-Eigenschaften:
.dotAll (/s).global (/g).hasIndices (/d).ignoreCase (/i).multiline (/m).sticky (/y).unicode (/u)Jeder reguläre Ausdruck hat auch die folgenden Eigenschaften:
.source [ES3]: Das Muster des regulären Ausdrucks
> /abc/ig.source
'abc'.flags [ES6]: Die Flags des regulären Ausdrucks
> /abc/ig.flags
'gi'.lastIndex [ES3]: Wird verwendet, wenn das Flag /g aktiviert ist. Siehe §43.7 „Die Flags /g und /y und die Eigenschaft .lastIndex“ für Details.
Mehrere Methoden im Zusammenhang mit regulären Ausdrücken geben sogenannte Trefferobjekte zurück, um detaillierte Informationen über die Stellen zu liefern, an denen ein regulärer Ausdruck mit einem Eingangsstring übereinstimmt. Diese Methoden sind:
RegExp.prototype.exec() gibt null oder einzelne Trefferobjekte zurück.String.prototype.match() gibt null oder einzelne Trefferobjekte zurück (wenn das Flag /g nicht gesetzt ist).String.prototype.matchAll() gibt ein Iterable von Trefferobjekten zurück (Flag /g muss gesetzt sein, sonst wird eine Ausnahme ausgelöst).Hier ist ein Beispiel:
assert.deepEqual(
/(a+)b/d.exec('ab aaab'),
{
0: 'ab',
1: 'a',
index: 0,
input: 'ab aaab',
groups: undefined,
indices: {
0: [0, 2],
1: [0, 1],
groups: undefined
},
}
);Das Ergebnis von .exec() ist ein Trefferobjekt für den ersten Treffer mit den folgenden Eigenschaften:
[0]: der vollständige Teilstring, der vom regulären Ausdruck übereingestimmt wurde[1]: Erfassung der nummerierten Gruppe 1 (usw.).index: wo ist der Treffer aufgetreten?.input: der String, der abgeglichen wurde.groups: Erfassungen von benannten Gruppen (siehe §43.6.4.2 „Benannte Erfassungsgruppen [ES2018]“).indices: die Indexbereiche der erfassten Gruppen/d aktiviert ist.Trefferindizes sind eine Funktion von Trefferobjekten: Wenn wir sie über das Flag /d des regulären Ausdrucks (Eigenschaft .hasIndices) aktivieren, zeichnen sie die Start- und Endindizes auf, wo Gruppen erfasst wurden.
So greifen wir auf die Erfassungen von nummerierten Gruppen zu:
const matchObj = /(a+)(b+)/d.exec('aaaabb');
assert.equal(
matchObj[1], 'aaaa'
);
assert.equal(
matchObj[2], 'bb'
);Aufgrund des Flags /d des regulären Ausdrucks hat matchObj auch eine Eigenschaft .indices, die für jede nummerierte Gruppe aufzeichnet, wo sie im Eingangsstring erfasst wurde.
assert.deepEqual(
matchObj.indices[1], [0, 4]
);
assert.deepEqual(
matchObj.indices[2], [4, 6]
);Die Erfassungen benannter Gruppen werden so zugegriffen:
const matchObj = /(?<as>a+)(?<bs>b+)/d.exec('aaaabb');
assert.equal(
matchObj.groups.as, 'aaaa');
assert.equal(
matchObj.groups.bs, 'bb');Ihre Indizes werden in matchObj.indices.groups gespeichert.
assert.deepEqual(
matchObj.indices.groups.as, [0, 4]);
assert.deepEqual(
matchObj.indices.groups.bs, [4, 6]);Ein wichtiger Anwendungsfall für Trefferindizes sind Parser, die genau aufzeigen, wo sich ein syntaktischer Fehler befindet. Der folgende Code löst ein verwandtes Problem: Er zeigt an, wo der zitierte Inhalt beginnt und wo er endet (siehe Demonstration am Ende).
const reQuoted = /“([^”]+)”/dgu;
function pointToQuotedText(str) {
const startIndices = new Set();
const endIndices = new Set();
for (const match of str.matchAll(reQuoted)) {
const [start, end] = match.indices[1];
startIndices.add(start);
endIndices.add(end);
}
let result = '';
for (let index=0; index < str.length; index++) {
if (startIndices.has(index)) {
result += '[';
} else if (endIndices.has(index+1)) {
result += ']';
} else {
result += ' ';
}
}
return result;
}
assert.equal(
pointToQuotedText(
'They said “hello” and “goodbye”.'),
' [ ] [ ] '
);Standardmäßig passen reguläre Ausdrücke überall in einem String.
> /a/.test('__a__')
trueWir können das ändern, indem wir Assertions wie ^ verwenden oder das Flag /y nutzen.
> /^a/.test('__a__')
false
> /^a/.test('a__')
trueregExp.test(str): Gibt es einen Treffer? [ES3]Die Methode .test() des regulären Ausdrucks gibt true zurück, wenn regExp mit str übereinstimmt.
> /bc/.test('ABCD')
false
> /bc/i.test('ABCD')
true
> /\.mjs$/.test('main.mjs')
trueBei .test() sollten wir normalerweise das Flag /g vermeiden. Wenn wir es verwenden, erhalten wir im Allgemeinen nicht jedes Mal dasselbe Ergebnis, wenn wir die Methode aufrufen.
> const r = /a/g;
> r.test('aab')
true
> r.test('aab')
true
> r.test('aab')
falseDie Ergebnisse ergeben sich daraus, dass /a/ zwei Treffer im String hat. Nachdem all diese gefunden wurden, gibt .test() false zurück.
str.search(regExp): An welchem Index ist der Treffer? [ES3]Die String-Methode .search() gibt den ersten Index von str zurück, an dem ein Treffer für regExp vorhanden ist.
> '_abc_'.search(/abc/)
1
> 'main.mjs'.search(/\.mjs$/)
4regExp.exec(str): Erfassungsgruppen [ES3]Ohne das Flag /g gibt .exec() ein Trefferobjekt für den ersten Treffer von regExp in str zurück.
assert.deepEqual(
/(a+)b/.exec('ab aab'),
{
0: 'ab',
1: 'a',
index: 0,
input: 'ab aab',
groups: undefined,
}
);Das vorherige Beispiel enthielt eine einzelne nummerierte Gruppe. Das folgende Beispiel demonstriert benannte Gruppen:
assert.deepEqual(
/(?<as>a+)b/.exec('ab aab'),
{
0: 'ab',
1: 'a',
index: 0,
input: 'ab aab',
groups: { as: 'a' },
}
);Im Ergebnis von .exec() sehen wir, dass eine benannte Gruppe auch eine nummerierte Gruppe ist – ihre Erfassung existiert zweimal:
'1').groups.as). Bessere Alternative zum Abrufen aller Treffer:
str.matchAll(regExp) [ES2020]
Seit ECMAScript 2020 gibt es in JavaScript eine weitere Methode zum Abrufen aller Treffer: str.matchAll(regExp). Diese Methode ist einfacher zu verwenden und hat weniger Fallstricke.
Wenn wir alle Treffer eines regulären Ausdrucks abrufen möchten (nicht nur den ersten), müssen wir das Flag /g aktivieren. Dann können wir .exec() mehrmals aufrufen und erhalten jedes Mal einen Treffer. Nach dem letzten Treffer gibt .exec() null zurück.
> const regExp = /(a+)b/g;
> regExp.exec('ab aab')
{ 0: 'ab', 1: 'a', index: 0, input: 'ab aab', groups: undefined }
> regExp.exec('ab aab')
{ 0: 'aab', 1: 'aa', index: 3, input: 'ab aab', groups: undefined }
> regExp.exec('ab aab')
nullDaher können wir über alle Treffer wie folgt iterieren:
const regExp = /(a+)b/g;
const str = 'ab aab';
let match;
// Check for null via truthiness
// Alternative: while ((match = regExp.exec(str)) !== null)
while (match = regExp.exec(str)) {
console.log(match[1]);
}
// Output:
// 'a'
// 'aa' Seien Sie vorsichtig, wenn Sie reguläre Ausdrücke mit
/g teilen!
Das Teilen von regulären Ausdrücken mit /g birgt einige Fallstricke, die später erklärt werden.
Übung: Zitierten Text über
.exec() extrahieren
exercises/regexps/extract_quoted_test.mjs
str.match(regExp): Alle Treffer der Gruppe 0 erhalten [ES3]Ohne /g verhält sich .match() wie .exec() – es gibt ein einzelnes Trefferobjekt zurück.
Mit /g gibt .match() alle Teilstrings von str zurück, die mit regExp übereinstimmen:
> 'ab aab'.match(/(a+)b/g)
[ 'ab', 'aab' ]Wenn kein Treffer vorhanden ist, gibt .match() null zurück.
> 'xyz'.match(/(a+)b/g)
nullWir können den Nullish Coalescing Operator (??) verwenden, um uns vor null zu schützen.
const numberOfMatches = (str.match(regExp) ?? []).length;str.matchAll(regExp): Ein Iterable über alle Trefferobjekte erhalten [ES2020]So wird .matchAll() aufgerufen:
const matchIterable = str.matchAll(regExp);
Gegeben einen String und einen regulären Ausdruck, gibt .matchAll() ein Iterable über die Trefferobjekte aller Treffer zurück.
Im folgenden Beispiel verwenden wir Array.from(), um Iterables in Arrays umzuwandeln, damit wir sie besser vergleichen können.
> Array.from('-a-a-a'.matchAll(/-(a)/ug))
[
{ 0:'-a', 1:'a', index: 0, input: '-a-a-a', groups: undefined },
{ 0:'-a', 1:'a', index: 2, input: '-a-a-a', groups: undefined },
{ 0:'-a', 1:'a', index: 4, input: '-a-a-a', groups: undefined },
]Flag /g muss gesetzt sein:
> Array.from('-a-a-a'.matchAll(/-(a)/u))
TypeError: String.prototype.matchAll called with a non-global
RegExp argument.matchAll() wird von regExp.lastIndex nicht beeinflusst und ändert es auch nicht.
.matchAll().matchAll() könnte über .exec() wie folgt implementiert werden:
function* matchAll(str, regExp) {
if (!regExp.global) {
throw new TypeError('Flag /g must be set!');
}
const localCopy = new RegExp(regExp, regExp.flags);
let match;
while (match = localCopy.exec(str)) {
yield match;
}
}Eine lokale Kopie zu erstellen, stellt zwei Dinge sicher:
regex.lastIndex wird nicht geändert.localCopy.lastIndex ist null.Verwendung von matchAll()
const str = '"fee" "fi" "fo" "fum"';
const regex = /"([^"]*)"/g;
for (const match of matchAll(str, regex)) {
console.log(match[1]);
}
// Output:
// 'fee'
// 'fi'
// 'fo'
// 'fum'regExp.exec() vs. str.match() vs. str.matchAll()Die folgende Tabelle fasst die Unterschiede zwischen den drei Methoden zusammen:
Ohne /g |
Mit /g |
|
|---|---|---|
regExp.exec(str) |
Erstes Trefferobjekt | Nächstes Trefferobjekt oder null |
str.match(regExp) |
Erstes Trefferobjekt | Array von Gruppenerfassungen (Gruppe 0) |
str.matchAll(regExp) |
TypeError |
Iterable über Trefferobjekte |
str.replace() und str.replaceAll()Beide Ersetzungsmethoden haben zwei Parameter:
str.replace(searchValue, replacementValue)str.replaceAll(searchValue, replacementValue)searchValue kann sein:
replacementValue kann sein:
$ hat eine besondere Bedeutung und ermöglicht es uns, Erfassungen von Gruppen und mehr einzufügen (Details werden später erklärt).Die beiden Methoden unterscheiden sich wie folgt:
.replace() ersetzt das erste Vorkommen eines Strings oder eines regulären Ausdrucks ohne /g..replaceAll() ersetzt alle Vorkommen eines Strings oder eines regulären Ausdrucks mit /g.Diese Tabelle fasst zusammen, wie das funktioniert:
Suchen nach: → |
string | RegExp ohne /g |
RegExp mit /g |
|---|---|---|---|
.replace |
Erstes Vorkommen | Erstes Vorkommen | (Alle Vorkommen) |
.replaceAll |
Alle Vorkommen | TypeError |
Alle Vorkommen |
Die letzte Spalte von .replace() steht in Klammern, weil diese Methode lange vor .replaceAll() existierte und daher Funktionalität unterstützt, die jetzt über letztere Methode gehandhabt werden sollte. Wenn wir das ändern könnten, würde .replace() hier eine TypeError auslösen.
Wir untersuchen zuerst, wie .replace() und .replaceAll() einzeln funktionieren, wenn replacementValue ein einfacher String ist (ohne das Zeichen $). Dann untersuchen wir, wie beide von komplexeren Ersetzungswerten beeinflusst werden.
str.replace(searchValue, replacementValue) [ES3]Wie .replace() arbeitet, wird von seinem ersten Parameter searchValue beeinflusst:
Regulärer Ausdruck ohne /g: Ersetze den ersten Treffer dieses regulären Ausdrucks.
> 'aaa'.replace(/a/, 'x')
'xaa'String: Ersetze das erste Vorkommen dieses Strings (der String wird wörtlich interpretiert, nicht als regulärer Ausdruck).
> 'aaa'.replace('a', 'x')
'xaa'Regulärer Ausdruck mit /g: Ersetze alle Treffer dieses regulären Ausdrucks.
> 'aaa'.replace(/a/g, 'x')
'xxx'Empfehlung: Wenn .replaceAll() verfügbar ist, ist es besser, diese Methode in diesem Fall zu verwenden – ihr Zweck ist es, mehrere Vorkommen zu ersetzen.
Wenn wir jedes Vorkommen eines Strings ersetzen wollen, haben wir zwei Optionen:
Wir können .replaceAll() verwenden (eingeführt in ES2021).
Später in diesem Kapitel werden wir [die Hilfsfunktion escapeForRegExp()) kennenlernen, die uns hilft, einen String in einen regulären Ausdruck umzuwandeln, der diesen String mehrmals abgleicht (z. B. wird '*' zu /\*/g).
str.replaceAll(searchValue, replacementValue) [ES2021]Wie .replaceAll() arbeitet, wird von seinem ersten Parameter searchValue beeinflusst:
Regulärer Ausdruck mit /g: Ersetze alle Treffer dieses regulären Ausdrucks.
> 'aaa'.replaceAll(/a/g, 'x')
'xxx'String: Ersetze alle Vorkommen dieses Strings (der String wird wörtlich interpretiert, nicht als regulärer Ausdruck).
> 'aaa'.replaceAll('a', 'x')
'xxx'Regulärer Ausdruck ohne /g: Eine TypeError wird ausgelöst (da der Zweck von .replaceAll() das Ersetzen mehrerer Vorkommen ist).
> 'aaa'.replaceAll(/a/, 'x')
TypeError: String.prototype.replaceAll called with
a non-global RegExp argumentreplacementValue von .replace() und .replaceAll()Bisher haben wir den Parameter replacementValue nur mit einfachen Strings verwendet, aber er kann mehr. Wenn sein Wert ist:
Ein String, dann werden Treffer mit diesem String ersetzt. Das Zeichen $ hat eine besondere Bedeutung und ermöglicht es uns, Erfassungen von Gruppen und mehr einzufügen (lesen Sie weiter für Details).
Eine Funktion, dann werden Treffer durch Strings ersetzt, die über diese Funktion berechnet werden.
replacementValue ist ein StringWenn der Ersatzwert ein String ist, hat das Dollarzeichen eine besondere Bedeutung – es fügt Text ein, der vom regulären Ausdruck gefunden wurde:
| Text | Ergebnis |
|---|---|
$$ |
einzelnes $ |
$& |
vollständiger Treffer |
$` |
Text vor dem Treffer |
$' |
Text nach dem Treffer |
$n |
Erfassung der nummerierten Gruppe n (n > 0) |
$<name> |
Erfassung der benannten Gruppe name [ES2018] |
Beispiel: Einfügen des Texts vor, während und nach dem übereinstimmenden Teilstring.
> 'a1 a2'.replaceAll(/a/g, "($`|$&|$')")
'(|a|1 a2)1 (a1 |a|2)2'Beispiel: Einfügen der Erfassungen von nummerierten Gruppen.
> const regExp = /^([A-Za-z]+): (.*)$/ug;
> 'first: Jane'.replaceAll(regExp, 'KEY: $1, VALUE: $2')
'KEY: first, VALUE: Jane'Beispiel: Einfügen der Erfassungen von benannten Gruppen.
> const regExp = /^(?<key>[A-Za-z]+): (?<value>.*)$/ug;
> 'first: Jane'.replaceAll(regExp, 'KEY: $<key>, VALUE: $<value>')
'KEY: first, VALUE: Jane' Übung: Anführungszeichen über
.replace() und eine benannte Gruppe ändern
exercises/regexps/change_quotes_test.mjs
replacementValue ist eine FunktionWenn der Ersetzungswert eine Funktion ist, können wir jede Ersetzung berechnen. Im folgenden Beispiel multiplizieren wir jede gefundene nicht-negative Ganzzahl mit zwei.
assert.equal(
'3 cats and 4 dogs'.replaceAll(/[0-9]+/g, (all) => 2 * Number(all)),
'6 cats and 8 dogs'
);Die Ersetzungsfunktion erhält die folgenden Parameter. Beachten Sie, wie ähnlich sie den Trefferobjekten sind. Diese Parameter sind alle positionsbezogen, aber ich habe hinzugefügt, wie man sie benennen könnte
all: vollständiger Trefferg1: Erfassung der nummerierten Gruppe 1index: wo trat der Treffer auf?input: der String, in dem ersetzt wirdgroups [ES2018]: Erfassungen benannter Gruppen (ein Objekt). Immer der letzte Parameter.Wenn uns nur groups interessiert, können wir folgende Technik verwenden
const result = 'first=jane, last=doe'.replace(
/(?<key>[a-z]+)=(?<value>[a-z]+)/g,
(...args) => { // (A)
const groups = args.at(-1); // (B)
const {key, value} = groups;
return key.toUpperCase() + '=' + value.toUpperCase();
});
assert.equal(result, 'FIRST=JANE, LAST=DOE');Aufgrund des Rest-Parameters in Zeile A enthält args ein Array mit allen Parametern. Wir greifen über die Array-Methode .at() in Zeile B auf den letzten Parameter zu.
String.prototype.split() wird im Kapitel über Strings beschrieben. Sein erster Parameter von String.prototype.split() ist entweder ein String oder ein regulärer Ausdruck. Wenn letzteres der Fall ist, erscheinen Erfassungen von Gruppen im Ergebnis
> 'a:b : c'.split(':')
[ 'a', 'b ', ' c' ]
> 'a:b : c'.split(/ *: */)
[ 'a', 'b', 'c' ]
> 'a:b : c'.split(/( *):( *)/)
[ 'a', '', '', 'b', ' ', ' ', 'c' ]/g und /y und die Eigenschaft .lastIndex (fortgeschritten)In diesem Abschnitt untersuchen wir, wie die RegExp-Flags /g und /y funktionieren und wie sie von der RegExp-Eigenschaft .lastIndex abhängen. Wir werden auch einen interessanten Anwendungsfall für .lastIndex entdecken, der Sie überraschen dürfte.
/g und /yJede Methode reagiert anders auf /g und /y; dies gibt uns eine grobe allgemeine Vorstellung
/g (.global, ES3): Der reguläre Ausdruck soll mehrmals und überall in einem String übereinstimmen./y (.sticky, ES6): Jede Übereinstimmung innerhalb eines Strings muss unmittelbar auf eine vorherige Übereinstimmung folgen (die Übereinstimmungen "haften" aneinander).Wenn ein regulärer Ausdruck weder das Flag /g noch das Flag /y hat, erfolgt die Übereinstimmung einmal und beginnt am Anfang.
Mit entweder /g oder /y erfolgt die Übereinstimmung relativ zu einer "aktuellen Position" innerhalb des Eingabe-Strings. Diese Position wird in der Eigenschaft .lastIndex des regulären Ausdrucks gespeichert.
Es gibt drei Gruppen von regulären Ausdruck-bezogenen Methoden
Die String-Methoden .search(regExp) und .split(regExp) ignorieren /g und /y (und damit auch .lastIndex) vollständig.
Die RegExp-Methoden .exec(str) und .test(str) ändern sich auf zwei Arten, wenn entweder /g oder /y gesetzt ist.
Erstens erhalten wir mehrere Treffer, indem wir eine Methode wiederholt aufrufen. Jedes Mal gibt sie entweder ein weiteres Ergebnis (ein Trefferobjekt oder true) oder einen "Ende der Ergebnisse"-Wert (null oder false) zurück.
Zweitens wird die Eigenschaft .lastIndex des regulären Ausdrucks verwendet, um sich durch den Eingabe-String zu bewegen. Einerseits bestimmt .lastIndex, wo die Übereinstimmung beginnt
/g bedeutet, dass eine Übereinstimmung bei .lastIndex oder später beginnen muss.
/y bedeutet, dass eine Übereinstimmung bei .lastIndex beginnen muss. Das heißt, der Anfang des regulären Ausdrucks ist an .lastIndex gebunden.
Beachten Sie, dass ^ und $ weiterhin wie gewohnt funktionieren: Sie binden Treffer an den Anfang oder das Ende des Eingabe-Strings, es sei denn, .multiline ist gesetzt. Dann binden sie an den Anfang oder das Ende von Zeilen.
Andererseits wird .lastIndex auf eins plus den letzten Index des vorherigen Treffers gesetzt.
Alle anderen Methoden werden wie folgt beeinflusst
/g führt zu mehreren Treffern./y führt zu einem einzelnen Treffer, der bei .lastIndex beginnen muss./yg führt zu mehreren Treffern ohne Lücken.Dies war eine erste Übersicht. Die nächsten Abschnitte gehen auf weitere Details ein.
/g und /y beeinflusst?regExp.exec(str) [ES3]Ohne /g und /y ignoriert .exec() .lastIndex und gibt immer ein Trefferobjekt für den ersten Treffer zurück
> const re = /#/; re.lastIndex = 1;
> [re.exec('##-#'), re.lastIndex]
[{ 0: '#', index: 0, input: '##-#' }, 1]
> [re.exec('##-#'), re.lastIndex]
[{ 0: '#', index: 0, input: '##-#' }, 1]Mit /g muss der Treffer bei .lastIndex oder später beginnen. .lastIndex wird aktualisiert. Wenn es keinen Treffer gibt, wird null zurückgegeben.
> const re = /#/g; re.lastIndex = 1;
> [re.exec('##-#'), re.lastIndex]
[{ 0: '#', index: 1, input: '##-#' }, 2]
> [re.exec('##-#'), re.lastIndex]
[{ 0: '#', index: 3, input: '##-#' }, 4]
> [re.exec('##-#'), re.lastIndex]
[null, 0]Mit /y muss der Treffer exakt bei .lastIndex beginnen. .lastIndex wird aktualisiert. Wenn es keinen Treffer gibt, wird null zurückgegeben.
> const re = /#/y; re.lastIndex = 1;
> [re.exec('##-#'), re.lastIndex]
[{ 0: '#', index: 1, input: '##-#' }, 2]
> [re.exec('##-#'), re.lastIndex]
[null, 0]Mit /yg verhält sich .exec() genauso wie mit /y.
regExp.test(str) [ES3]Diese Methode verhält sich genauso wie .exec(), gibt aber anstelle eines Trefferobjekts true und anstelle von null false zurück.
Zum Beispiel ist ohne /g oder /y das Ergebnis immer true
> const re = /#/; re.lastIndex = 1;
> [re.test('##-#'), re.lastIndex]
[true, 1]
> [re.test('##-#'), re.lastIndex]
[true, 1]Mit /g gibt es zwei Treffer
> const re = /#/g; re.lastIndex = 1;
> [re.test('##-#'), re.lastIndex]
[true, 2]
> [re.test('##-#'), re.lastIndex]
[true, 4]
> [re.test('##-#'), re.lastIndex]
[false, 0]Mit /y gibt es nur einen Treffer
> const re = /#/y; re.lastIndex = 1;
> [re.test('##-#'), re.lastIndex]
[true, 2]
> [re.test('##-#'), re.lastIndex]
[false, 0]Mit /yg verhält sich .test() genauso wie mit /y.
str.match(regExp) [ES3]Ohne /g funktioniert .match() wie .exec(). Entweder ohne /y
> const re = /#/; re.lastIndex = 1;
> ['##-#'.match(re), re.lastIndex]
[{ 0: '#', index: 0, input: '##-#' }, 1]
> ['##-#'.match(re), re.lastIndex]
[{ 0: '#', index: 0, input: '##-#' }, 1]Oder mit /y
> const re = /#/y; re.lastIndex = 1;
> ['##-#'.match(re), re.lastIndex]
[{ 0: '#', index: 1, input: '##-#' }, 2]
> ['##-#'.match(re), re.lastIndex]
[null, 0]Mit /g erhalten wir alle Treffer (Gruppe 0) in einem Array. .lastIndex wird ignoriert und auf Null zurückgesetzt.
> const re = /#/g; re.lastIndex = 1;
> '##-#'.match(re)
['#', '#', '#']
> re.lastIndex
0/yg funktioniert wie /g, aber es gibt keine Lücken zwischen den Treffern
> const re = /#/yg; re.lastIndex = 1;
> '##-#'.match(re)
['#', '#']
> re.lastIndex
0str.matchAll(regExp) [ES2020]Wenn /g nicht gesetzt ist, löst .matchAll() eine Ausnahme aus
> const re = /#/y; re.lastIndex = 1;
> '##-#'.matchAll(re)
TypeError: String.prototype.matchAll called with
a non-global RegExp argumentWenn /g gesetzt ist, beginnt die Übereinstimmung bei .lastIndex und diese Eigenschaft wird nicht geändert
> const re = /#/g; re.lastIndex = 1;
> Array.from('##-#'.matchAll(re))
[
{ 0: '#', index: 1, input: '##-#' },
{ 0: '#', index: 3, input: '##-#' },
]
> re.lastIndex
1Wenn /yg gesetzt ist, ist das Verhalten dasselbe wie bei /g, aber es gibt keine Lücken zwischen den Treffern
> const re = /#/yg; re.lastIndex = 1;
> Array.from('##-#'.matchAll(re))
[
{ 0: '#', index: 1, input: '##-#' },
]
> re.lastIndex
1str.replace(regExp, str) [ES3]Ohne /g und /y wird nur das erste Vorkommen ersetzt
> const re = /#/; re.lastIndex = 1;
> '##-#'.replace(re, 'x')
'x#-#'
> re.lastIndex
1Mit /g werden alle Vorkommen ersetzt. .lastIndex wird ignoriert, aber auf Null zurückgesetzt.
> const re = /#/g; re.lastIndex = 1;
> '##-#'.replace(re, 'x')
'xx-x'
> re.lastIndex
0Mit /y wird nur das (erste) Vorkommen bei .lastIndex ersetzt. .lastIndex wird aktualisiert.
> const re = /#/y; re.lastIndex = 1;
> '##-#'.replace(re, 'x')
'#x-#'
> re.lastIndex
2/yg funktioniert wie /g, aber Lücken zwischen Treffern sind nicht erlaubt
> const re = /#/yg; re.lastIndex = 1;
> '##-#'.replace(re, 'x')
'xx-#'
> re.lastIndex
0str.replaceAll(regExp, str) [ES2021].replaceAll() funktioniert wie .replace(), löst aber eine Ausnahme aus, wenn /g nicht gesetzt ist
> const re = /#/y; re.lastIndex = 1;
> '##-#'.replaceAll(re, 'x')
TypeError: String.prototype.replaceAll called
with a non-global RegExp argument/g und /y und wie man mit ihnen umgehtWir werden uns zuerst vier Fallstricke von /g und /y ansehen und dann Wege, um mit diesen Fallstricken umzugehen.
/g oder /y inline einfügenEin regulärer Ausdruck mit /g kann nicht inline verwendet werden. Zum Beispiel wird in der folgenden while-Schleife der reguläre Ausdruck jedes Mal, wenn die Bedingung geprüft wird, neu erstellt. Daher ist sein .lastIndex immer Null und die Schleife endet nie.
let matchObj;
// Infinite loop
while (matchObj = /a+/g.exec('bbbaabaaa')) {
console.log(matchObj[0]);
}Mit /y ist das Problem dasselbe.
/g oder /y kann Code brechenWenn Code einen regulären Ausdruck mit /g erwartet und eine Schleife über die Ergebnisse von .exec() oder .test() hat, kann ein regulärer Ausdruck ohne /g eine Endlosschleife verursachen
function collectMatches(regExp, str) {
const matches = [];
let matchObj;
// Infinite loop
while (matchObj = regExp.exec(str)) {
matches.push(matchObj[0]);
}
return matches;
}
collectMatches(/a+/, 'bbbaabaaa'); // Missing: flag /gWarum gibt es eine Endlosschleife? Weil .exec() immer das erste Ergebnis, ein Trefferobjekt, zurückgibt und niemals null.
Mit /y ist das Problem dasselbe.
/g oder /y kann Code brechenBei .test() gibt es einen weiteren Vorbehalt: Es wird von .lastIndex beeinflusst. Daher muss, wenn wir genau einmal prüfen wollen, ob ein regulärer Ausdruck mit einem String übereinstimmt, der reguläre Ausdruck kein /g haben. Andernfalls erhalten wir im Allgemeinen bei jedem Aufruf von .test() ein anderes Ergebnis
> const regExp = /^X/g;
> [regExp.test('Xa'), regExp.lastIndex]
[ true, 1 ]
> [regExp.test('Xa'), regExp.lastIndex]
[ false, 0 ]
> [regExp.test('Xa'), regExp.lastIndex]
[ true, 1 ]Die erste Invocage liefert einen Treffer und aktualisiert .lastIndex. Die zweite Invocage findet keinen Treffer und setzt .lastIndex auf Null zurück.
Wenn wir einen regulären Ausdruck speziell für .test() erstellen, werden wir wahrscheinlich kein /g hinzufügen. Die Wahrscheinlichkeit, auf /g zu stoßen, steigt jedoch, wenn wir denselben regulären Ausdruck zum Ersetzen und zum Testen verwenden.
Auch hier besteht dieses Problem auch mit /y
> const regExp = /^X/y;
> regExp.test('Xa')
true
> regExp.test('Xa')
false
> regExp.test('Xa')
true.lastIndex nicht Null istAngesichts all der Operationen mit regulären Ausdrücken, die von .lastIndex beeinflusst werden, müssen wir bei vielen Algorithmen darauf achten, dass .lastIndex am Anfang Null ist. Andernfalls können wir unerwartete Ergebnisse erhalten
function countMatches(regExp, str) {
let count = 0;
while (regExp.test(str)) {
count++;
}
return count;
}
const myRegExp = /a/g;
myRegExp.lastIndex = 4;
assert.equal(
countMatches(myRegExp, 'babaa'), 1); // should be 3Normalerweise ist .lastIndex bei neu erstellten regulären Ausdrücken Null und wir ändern ihn nicht explizit, wie wir es im Beispiel getan haben. Aber .lastIndex kann immer noch nicht Null sein, wenn wir den regulären Ausdruck mehrmals verwenden.
/g und /y vermeidetAls Beispiel für den Umgang mit /g und .lastIndex greifen wir countMatches() aus dem vorherigen Beispiel wieder auf. Wie verhindern wir, dass ein falscher regulärer Ausdruck unseren Code bricht? Schauen wir uns drei Ansätze an.
Erstens können wir eine Ausnahme auslösen, wenn /g nicht gesetzt ist oder .lastIndex nicht Null ist
function countMatches(regExp, str) {
if (!regExp.global) {
throw new Error('Flag /g of regExp must be set');
}
if (regExp.lastIndex !== 0) {
throw new Error('regExp.lastIndex must be zero');
}
let count = 0;
while (regExp.test(str)) {
count++;
}
return count;
}Zweitens können wir den Parameter klonen. Das hat den zusätzlichen Vorteil, dass regExp nicht verändert wird.
function countMatches(regExp, str) {
const cloneFlags = regExp.flags + (regExp.global ? '' : 'g');
const clone = new RegExp(regExp, cloneFlags);
let count = 0;
while (clone.test(str)) {
count++;
}
return count;
}.lastIndex oder Flags beeinflusst wirdMehrere Operationen mit regulären Ausdrücken werden nicht von .lastIndex oder von Flags beeinflusst. Zum Beispiel ignoriert .match() .lastIndex, wenn /g vorhanden ist
function countMatches(regExp, str) {
if (!regExp.global) {
throw new Error('Flag /g of regExp must be set');
}
return (str.match(regExp) ?? []).length;
}
const myRegExp = /a/g;
myRegExp.lastIndex = 4;
assert.equal(countMatches(myRegExp, 'babaa'), 3); // OK!Hier funktioniert countMatches(), auch wenn wir .lastIndex nicht geprüft oder korrigiert haben.
.lastIndex: Übereinstimmung an einem bestimmten Index startenNeben der Speicherung von Zuständen kann .lastIndex auch verwendet werden, um die Übereinstimmung an einem bestimmten Index zu starten. Dieser Abschnitt beschreibt, wie.
Da .test() von /y und .lastIndex beeinflusst wird, können wir es verwenden, um zu prüfen, ob ein regulärer Ausdruck regExp mit einem String str an einem bestimmten index übereinstimmt
function matchesStringAt(regExp, str, index) {
if (!regExp.sticky) {
throw new Error('Flag /y of regExp must be set');
}
regExp.lastIndex = index;
return regExp.test(str);
}
assert.equal(
matchesStringAt(/x+/y, 'aaxxx', 0), false);
assert.equal(
matchesStringAt(/x+/y, 'aaxxx', 2), true);regExp ist aufgrund von /y an .lastIndex gebunden.
Beachten Sie, dass wir die Assertion ^ nicht verwenden dürfen, die regExp an den Anfang des Eingabe-Strings binden würde.
.search() ermöglicht es uns, den Speicherort zu finden, an dem ein regulärer Ausdruck übereinstimmt
> '#--#'.search(/#/)
0Leider können wir nicht ändern, wo .search() nach Übereinstimmungen sucht. Als Workaround können wir .exec() für die Suche verwenden
function searchAt(regExp, str, index) {
if (!regExp.global && !regExp.sticky) {
throw new Error('Either flag /g or flag /y of regExp must be set');
}
regExp.lastIndex = index;
const match = regExp.exec(str);
if (match) {
return match.index;
} else {
return -1;
}
}
assert.equal(
searchAt(/#/g, '#--#', 0), 0);
assert.equal(
searchAt(/#/g, '#--#', 1), 3);Wenn .replace() ohne /g und mit /y verwendet wird, macht es eine Ersetzung – wenn eine Übereinstimmung bei .lastIndex vorhanden ist
function replaceOnceAt(str, regExp, replacement, index) {
if (!(regExp.sticky && !regExp.global)) {
throw new Error('Flag /y must be set, flag /g must not be set');
}
regExp.lastIndex = index;
return str.replace(regExp, replacement);
}
assert.equal(
replaceOnceAt('aa aaaa a', /a+/y, 'X', 0), 'X aaaa a');
assert.equal(
replaceOnceAt('aa aaaa a', /a+/y, 'X', 3), 'aa X a');
assert.equal(
replaceOnceAt('aa aaaa a', /a+/y, 'X', 8), 'aa aaaa X');.lastIndexDie Eigenschaft .lastIndex des regulären Ausdrucks hat zwei erhebliche Nachteile
.lastIndex ist bei Operationen mit regulären Ausdrücken inkonsistent.Auf der positiven Seite bietet .lastIndex auch zusätzliche nützliche Funktionalität: Wir können diktieren, wo die Übereinstimmung beginnen soll (für einige Operationen).
.global (/g) und .sticky (/y)Die folgenden beiden Methoden werden von /g und /y überhaupt nicht beeinflusst
String.prototype.search()String.prototype.split()Diese Tabelle erklärt, wie die verbleibenden Methoden, die mit regulären Ausdrücken zu tun haben, von diesen beiden Flags beeinflusst werden
/ |
/g |
/y |
/yg |
|
|---|---|---|---|---|
r.exec(s) |
{i:0} |
{i:1} |
{i:1} |
{i:1} |
.lI unch |
.lI upd |
.lI upd |
.lI upd |
|
r.test(s) |
true |
true |
true |
true |
.lI unch |
.lI upd |
.lI upd |
.lI upd |
|
s.match(r) |
{i:0} |
["#","#","#"] |
{i:1} |
["#","#"] |
.lI unch |
.lI reset |
.lI upd |
.lI reset |
|
s.matchAll(r) |
TypeError |
[{i:1}, {i:3}] |
TypeError |
[{i:1}] |
.lI unch |
.lI unch |
|||
s.replace(r, 'x') |
"x#-#" |
"xx-x" |
"#x-#" |
"xx-#" |
.lI unch |
.lI reset |
.lI upd |
.lI reset |
|
s.replaceAll(r, 'x') |
TypeError |
"xx-x" |
TypeError |
"xx-#" |
.lI reset |
.lI reset |
Variablen
const r = /#/; r.lastIndex = 1;
const s = '##-#';Abkürzungen
{i:2}: Trefferobjekt, dessen Eigenschaft .index den Wert 2 hat.lI upd: .lastIndex wird aktualisiert.lI reset: .lastIndex wird auf Null zurückgesetzt.lI unch: .lastIndex bleibt unverändert Das Node.js-Skript, das die vorherige Tabelle generiert hat
Die vorherige Tabelle wurde über ein Node.js-Skript generiert.
Die folgende Funktion escaped willkürlichen Text, sodass er exakt übereinstimmt, wenn wir ihn in einen regulären Ausdruck einfügen
function escapeForRegExp(str) {
return str.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'); // (A)
}
assert.equal(escapeForRegExp('[yes?]'), String.raw`\[yes\?\]`);
assert.equal(escapeForRegExp('_g_'), String.raw`_g_`);In Zeile A escapen wir alle Syntaxzeichen. Wir müssen selektiv sein, da das Flag /u des regulären Ausdrucks viele Escape-Sequenzen verbietet – z. B.: \a \: \-
escapeForRegExp() hat zwei Anwendungsfälle
new RegExp() erstellen..replace() ersetzen (und können .replaceAll() nicht verwenden)..replace() erlaubt uns nur, Plain Text einmal zu ersetzen. Mit escapeForRegExp() können wir diese Einschränkung umgehen
const plainText = ':-)';
const regExp = new RegExp(escapeForRegExp(plainText), 'ug');
assert.equal(
':-) :-) :-)'.replace(regExp, '🙂'), '🙂 🙂 🙂');Manchmal benötigen wir einen regulären Ausdruck, der alles oder nichts matcht – zum Beispiel als Standardwert.
Alles matchen: /(?:)/
Die leere Gruppe () matcht alles. Wir machen sie nicht-erfassend (über ?:), um unnötige Arbeit zu vermeiden.
> /(?:)/.test('')
true
> /(?:)/.test('abc')
trueNichts matchen: /.^/
^ matcht nur am Anfang eines Strings. Der Punkt bewegt die Übereinstimmung über das erste Zeichen hinaus, und nun matcht ^ nicht mehr.
> /.^/.test('')
false
> /.^/.test('abc')
false