Dieses Kapitel erklärt neue Funktionen für reguläre Ausdrücke in ECMAScript 6. Es ist hilfreich, wenn Sie mit den regulären Ausdrücken von ES5 und Unicode vertraut sind. Konsultieren Sie bei Bedarf die folgenden beiden Kapitel von „Speaking JavaScript“
/y (sticky)RegExp.prototype.exec(str)RegExp.prototype.test(str)String.prototype.search(regex)String.prototype.match(regex)String.prototype.split(separator, limit)String.prototype.replace(search, replacement)/u (unicode).) passt auf Codepoints, nicht auf Code-UnitsflagsRegExp() kann als Kopierkonstruktor verwendet werdenexec()Die folgenden Funktionen für reguläre Ausdrücke sind neu in ECMAScript 6
/y (sticky) verankert jede Übereinstimmung eines regulären Ausdrucks am Ende der vorherigen Übereinstimmung./u (unicode) behandelt Surrogatpaare (wie \uD83D\uDE80) als Codepoints und erlaubt Ihnen, Unicode-Codepoint-Escapes (wie \u{1F680}) in regulären Ausdrücken zu verwenden.flags gibt Ihnen Zugriff auf die Flags eines regulären Ausdrucks, so wie source Ihnen bereits in ES5 Zugriff auf das Muster gibt > /abc/ig.source // ES5
'abc'
> /abc/ig.flags // ES6
'gi'
RegExp() verwenden, um eine Kopie eines regulären Ausdrucks zu erstellen > new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
/y (sticky) Das neue Flag /y ändert zwei Dinge bei der Übereinstimmung eines regulären Ausdrucks re mit einem String
re.lastIndex: Die Übereinstimmung muss bei re.lastIndex beginnen (dem Index nach der vorherigen Übereinstimmung). Dieses Verhalten ähnelt dem Anker ^, aber mit diesem Anker müssen Übereinstimmungen immer bei Index 0 beginnen.re.lastIndex auf den Index nach der Übereinstimmung gesetzt. Dieses Verhalten ähnelt dem /g-Flag. Wie /g wird /y normalerweise verwendet, um mehrmals abzugleichen.Der Hauptanwendungsfall für dieses Matching-Verhalten ist das Tokenisieren, bei dem jede Übereinstimmung unmittelbar auf die vorhergehende folgen soll. Ein Beispiel für Tokenisieren über einen Sticky-Regulären-Ausdruck und exec() wird später gegeben.
Betrachten wir, wie sich verschiedene Operationen mit regulären Ausdrücken auf das /y-Flag auswirken. Die folgenden Tabellen geben einen Überblick. Im Folgenden werde ich weitere Details erläutern.
Methoden von regulären Ausdrücken (re ist der reguläre Ausdruck, auf den eine Methode aufgerufen wird)
| Flags | Starten des Abgleichs | Verankert an | Ergebnis, wenn Übereinstimmung | Keine Übereinstimmung | re.lastIndex | |
|---|---|---|---|---|---|---|
| exec() | – | 0 | – | Match-Objekt | null | unverändert |
| /g | re.lastIndex |
– | Match-Objekt | null | Index nach der Übereinstimmung | |
| /y | re.lastIndex |
re.lastIndex |
Match-Objekt | null | Index nach der Übereinstimmung | |
| /gy | re.lastIndex |
re.lastIndex |
Match-Objekt | null | Index nach der Übereinstimmung | |
| test() | (Beliebig) | (wie exec()) | (wie exec()) | true | false | (wie exec()) |
Methoden von Strings (str ist der String, auf den eine Methode aufgerufen wird, r ist der Parameter des regulären Ausdrucks)
| Flags | Starten des Abgleichs | Verankert an | Ergebnis, wenn Übereinstimmung | Keine Übereinstimmung | r.lastIndex | |
|---|---|---|---|---|---|---|
| search() | –, /g | 0 | – | Index der Übereinstimmung | -1 | unverändert |
| /y, /gy | 0 | 0 | Index der Übereinstimmung | -1 | unverändert | |
| match() | – | 0 | – | Match-Objekt | null | unverändert |
| /y | r.lastIndex |
r.lastIndex |
Match-Objekt | null | Index nach | |
| Übereinstimmung | ||||||
| /g | Nach vorheriger | – | Array mit Übereinstimmungen | null | 0 | |
| Übereinstimmung (Schleife) | ||||||
| /gy | Nach vorheriger | Nach vorheriger | Array mit Übereinstimmungen | null | 0 | |
| Übereinstimmung (Schleife) | Übereinstimmung | |||||
| split() | –, /g | Nach vorheriger | – | Array mit Strings | [str] |
unverändert |
| Übereinstimmung (Schleife) | zwischen Übereinstimmungen | |||||
| /y, /gy | Nach vorheriger | Nach vorheriger | Array mit leeren Strings | [str] |
unverändert | |
| Übereinstimmung (Schleife) | Übereinstimmung | zwischen Übereinstimmungen | ||||
| replace() | – | 0 | – | Erste Übereinstimmung ersetzt | Keine Ersetzung | unverändert |
| /y | 0 | 0 | Erste Übereinstimmung ersetzt | Keine Ersetzung | unverändert | |
| /g | Nach vorheriger | – | Alle Übereinstimmungen ersetzt | Keine Ersetzung | unverändert | |
| Übereinstimmung (Schleife) | ||||||
| /gy | Nach vorheriger | Nach vorheriger | Alle Übereinstimmungen ersetzt | Keine Ersetzung | unverändert | |
| Übereinstimmung (Schleife) | Übereinstimmung |
RegExp.prototype.exec(str) Wenn /g nicht gesetzt ist, beginnt der Abgleich immer am Anfang, überspringt aber so lange, bis eine Übereinstimmung gefunden wird. REGEX.lastIndex wird nicht geändert.
const REGEX = /a/;
REGEX.lastIndex = 7; // ignored
const match = REGEX.exec('xaxa');
console.log(match.index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
Wenn /g gesetzt ist, beginnt der Abgleich bei REGEX.lastIndex und überspringt, bis eine Übereinstimmung gefunden wird. REGEX.lastIndex wird auf die Position nach der Übereinstimmung gesetzt. Das bedeutet, dass Sie alle Übereinstimmungen erhalten, wenn Sie so lange schleifen, bis exec() null zurückgibt.
const REGEX = /a/g;
REGEX.lastIndex = 2;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4 (updated)
// No match at index 4 or later
console.log(REGEX.exec('xaxa')); // null
Wenn nur /y gesetzt ist, beginnt der Abgleich bei REGEX.lastIndex und ist an dieser Position verankert (kein Überspringen, bis eine Übereinstimmung gefunden wird). REGEX.lastIndex wird ähnlich wie bei gesetztem /g aktualisiert.
const REGEX = /a/y;
// No match at index 2
REGEX.lastIndex = 2;
console.log(REGEX.exec('xaxa')); // null
// Match at index 3
REGEX.lastIndex = 3;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4
Das Setzen von sowohl /y als auch /g ist dasselbe wie nur /y zu setzen.
RegExp.prototype.test(str) test() funktioniert wie exec(), gibt aber true oder false zurück (anstelle eines Match-Objekts oder null), wenn der Abgleich erfolgreich ist oder fehlschlägt
const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log(REGEX.test('xaxa')); // false
REGEX.lastIndex = 3;
console.log(REGEX.test('xaxa')); // true
console.log(REGEX.lastIndex); // 4
String.prototype.search(regex) search() ignoriert das Flag /g und lastIndex (das weder geändert noch verwendet wird). Beginnend am Anfang des Strings sucht es nach der ersten Übereinstimmung und gibt deren Index zurück (oder -1, wenn keine Übereinstimmung gefunden wurde)
const REGEX = /a/;
REGEX.lastIndex = 2; // ignored
console.log('xaxa'.search(REGEX)); // 1
Wenn Sie das Flag /y setzen, wird lastIndex immer noch ignoriert, aber der reguläre Ausdruck ist jetzt bei Index 0 verankert.
const REGEX = /a/y;
REGEX.lastIndex = 1; // ignored
console.log('xaxa'.search(REGEX)); // -1 (no match)
String.prototype.match(regex) match() hat zwei Modi
/g nicht gesetzt ist, funktioniert es wie exec()./g gesetzt ist, gibt es ein Array mit den übereinstimmenden String-Teilen oder null zurück.Wenn das Flag /g nicht gesetzt ist, erfasst match() Gruppen wie exec()
{
const REGEX = /a/;
REGEX.lastIndex = 7; // ignored
console.log('xaxa'.match(REGEX).index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
}
{
const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log('xaxa'.match(REGEX)); // null
REGEX.lastIndex = 3;
console.log('xaxa'.match(REGEX).index); // 3
console.log(REGEX.lastIndex); // 4
}
Wenn nur das Flag /g gesetzt ist, gibt match() alle übereinstimmenden Teilstrings in einem Array zurück (oder null). Der Abgleich beginnt immer bei Position 0.
const REGEX = /a|b/g;
REGEX.lastIndex = 7;
console.log('xaxb'.match(REGEX)); // ['a', 'b']
console.log(REGEX.lastIndex); // 0
Wenn Sie zusätzlich das Flag /y setzen, wird der Abgleich weiterhin wiederholt durchgeführt, wobei der reguläre Ausdruck am Index nach der vorherigen Übereinstimmung (oder 0) verankert wird.
const REGEX = /a|b/gy;
REGEX.lastIndex = 0; // ignored
console.log('xab'.match(REGEX)); // null
REGEX.lastIndex = 1; // ignored
console.log('xab'.match(REGEX)); // null
console.log('ab'.match(REGEX)); // ['a', 'b']
console.log('axb'.match(REGEX)); // ['a']
String.prototype.split(separator, limit) Die vollständigen Details zu split() werden in Speaking JavaScript erklärt.
Für ES6 ist es interessant zu sehen, wie sich die Dinge ändern, wenn Sie das Flag /y verwenden.
Mit /y muss der String mit einem Trennzeichen beginnen
> 'x##'.split(/#/y) // no match
[ 'x##' ]
> '##x'.split(/#/y) // 2 matches
[ '', '', 'x' ]
Nachfolgende Trennzeichen werden nur erkannt, wenn sie unmittelbar auf das erste Trennzeichen folgen
> '#x#'.split(/#/y) // 1 match
[ '', 'x#' ]
> '##'.split(/#/y) // 2 matches
[ '', '', '' ]
Das bedeutet, dass der String vor dem ersten Trennzeichen und die Strings zwischen den Trennzeichen immer leer sind.
Wie üblich können Sie Gruppen verwenden, um Teile der Trennzeichen in das Ergebnis-Array aufzunehmen
> '##'.split(/(#)/y)
[ '', '#', '', '#', '' ]
String.prototype.replace(search, replacement) Ohne das Flag /g ersetzt replace() nur die erste Übereinstimmung
const REGEX = /a/;
// One match
console.log('xaxa'.replace(REGEX, '-')); // 'x-xa'
Wenn nur /y gesetzt ist, erhalten Sie ebenfalls höchstens eine Übereinstimmung, aber diese ist immer am Anfang des Strings verankert. lastIndex wird ignoriert und unverändert gelassen.
const REGEX = /a/y;
// Anchored to beginning of string, no match
REGEX.lastIndex = 1; // ignored
console.log('xaxa'.replace(REGEX, '-')); // 'xaxa'
console.log(REGEX.lastIndex); // 1 (unchanged)
// One match
console.log('axa'.replace(REGEX, '-')); // '-xa'
Mit gesetztem /g ersetzt replace() alle Übereinstimmungen
const REGEX = /a/g;
// Multiple matches
console.log('xaxa'.replace(REGEX, '-')); // 'x-x-'
Mit gesetztem /gy ersetzt replace() alle Übereinstimmungen, aber jede Übereinstimmung ist am Ende der vorherigen Übereinstimmung verankert
const REGEX = /a/gy;
// Multiple matches
console.log('aaxa'.replace(REGEX, '-')); // '--xa'
Der Parameter replacement kann auch eine Funktion sein. Konsultieren Sie „Speaking JavaScript“ für Details.
Der Hauptanwendungsfall für Sticky-Matching ist das *Tokenisieren*, d. h. das Umwandeln eines Textes in eine Folge von Tokens. Ein wichtiges Merkmal des Tokenisierens ist, dass Tokens Fragmente des Textes sind und dass keine Lücken zwischen ihnen bestehen dürfen. Daher ist Sticky-Matching hier perfekt.
function tokenize(TOKEN_REGEX, str) {
const result = [];
let match;
while (match = TOKEN_REGEX.exec(str)) {
result.push(match[1]);
}
return result;
}
const TOKEN_GY = /\s*(\+|[0-9]+)\s*/gy;
const TOKEN_G = /\s*(\+|[0-9]+)\s*/g;
In einer gültigen Token-Sequenz ergeben Sticky-Matching und Nicht-Sticky-Matching die gleiche Ausgabe
> tokenize(TOKEN_GY, '3 + 4')
[ '3', '+', '4' ]
> tokenize(TOKEN_G, '3 + 4')
[ '3', '+', '4' ]
Wenn jedoch nicht-tokenisierter Text im String vorhanden ist, stoppt Sticky-Matching das Tokenisieren, während Nicht-Sticky-Matching den nicht-tokenisierten Text überspringt
> tokenize(TOKEN_GY, '3x + 4')
[ '3' ]
> tokenize(TOKEN_G, '3x + 4')
[ '3', '+', '4' ]
Das Verhalten von Sticky-Matching während des Tokenisierens hilft bei der Fehlerbehandlung.
Wenn Sie Sticky-Matching manuell implementieren wollten, würden Sie es wie folgt tun: Die Funktion execSticky() funktioniert wie RegExp.prototype.exec() im Sticky-Modus.
function execSticky(regex, str) {
// Anchor the regex to the beginning of the string
let matchSource = regex.source;
if (!matchSource.startsWith('^')) {
matchSource = '^' + matchSource;
}
// Ensure that instance property `lastIndex` is updated
let matchFlags = regex.flags; // ES6 feature!
if (!regex.global) {
matchFlags = matchFlags + 'g';
}
const matchRegex = new RegExp(matchSource, matchFlags);
// Ensure we start matching `str` at `regex.lastIndex`
const matchOffset = regex.lastIndex;
const matchStr = str.slice(matchOffset);
let match = matchRegex.exec(matchStr);
// Translate indices from `matchStr` to `str`
regex.lastIndex = matchRegex.lastIndex + matchOffset;
match.index = match.index + matchOffset;
return match;
}
/u (unicode) Das Flag /u schaltet einen speziellen Unicode-Modus für einen regulären Ausdruck ein. Dieser Modus hat zwei Funktionen
\u{1F42A} verwenden, um Zeichen über Codepoints anzugeben. Normale Unicode-Escapes wie \u03B1 haben nur einen Bereich von vier Hexadezimalziffern (was der Basic Multilingual Plane entspricht).Ein Abschnitt im Kapitel über Unicode enthält weitere Informationen zu Escape-Sequenzen. Ich werde die Konsequenzen von Funktion 2 als Nächstes erläutern. Anstelle von Unicode-Codepoint-Escapes (z. B. \u{1F680}) verwende ich zwei UTF-16 Code-Units (z. B. \uD83D\uDE80). Das macht deutlich, dass Surrogatpaare im Unicode-Modus gruppiert werden und sowohl im Unicode-Modus als auch im Nicht-Unicode-Modus funktionieren.
> '\u{1F680}' === '\uD83D\uDE80' // code point vs. surrogate pairs
true
Im Nicht-Unicode-Modus wird ein alleinstehender Surrogate in einem regulären Ausdruck sogar innerhalb von (Surrogatpaare kodierenden) Codepoints gefunden
> /\uD83D/.test('\uD83D\uDC2A')
true
Im Unicode-Modus werden Surrogatpaare zu atomaren Einheiten und alleinstehende Surrogates werden „innerhalb“ von ihnen nicht gefunden
> /\uD83D/u.test('\uD83D\uDC2A')
false
Tatsächlich alleinstehende Surrogates werden immer noch gefunden
> /\uD83D/u.test('\uD83D \uD83D\uDC2A')
true
> /\uD83D/u.test('\uD83D\uDC2A \uD83D')
true
Im Unicode-Modus können Sie Codepoints in Zeichenklassen einfügen und sie werden nicht mehr als zwei Zeichen interpretiert.
> /^[\uD83D\uDC2A]$/u.test('\uD83D\uDC2A')
true
> /^[\uD83D\uDC2A]$/.test('\uD83D\uDC2A')
false
> /^[\uD83D\uDC2A]$/u.test('\uD83D')
false
> /^[\uD83D\uDC2A]$/.test('\uD83D')
true
.) passt auf Codepoints, nicht auf Code-Units Im Unicode-Modus passt der Punkt-Operator auf Codepoints (ein oder zwei Code-Units). Im Nicht-Unicode-Modus passt er auf einzelne Code-Units. Zum Beispiel
> '\uD83D\uDE80'.match(/./gu).length
1
> '\uD83D\uDE80'.match(/./g).length
2
Im Unicode-Modus gelten Quantifizierer für Codepoints (ein oder zwei Code-Units). Im Nicht-Unicode-Modus gelten sie für einzelne Code-Units. Zum Beispiel
> /\uD83D\uDE80{2}/u.test('\uD83D\uDE80\uD83D\uDE80')
true
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uD83D\uDE80')
false
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uDE80')
true
flags In ECMAScript 6 haben reguläre Ausdrücke die folgenden Dateneigenschaften
sourceflagsglobal, ignoreCase, multiline, sticky, unicodelastIndexNebenbei bemerkt, ist lastIndex die einzige Instanz-Eigenschaft, alle anderen Dateneigenschaften werden über interne Instanz-Eigenschaften und Getter wie get RegExp.prototype.global implementiert.
Die Eigenschaft source (die es bereits in ES5 gab) enthält das Muster des regulären Ausdrucks als String
> /abc/ig.source
'abc'
Die Eigenschaft flags ist neu, sie enthält die Flags als String, mit einem Zeichen pro Flag
> /abc/ig.flags
'gi'
Sie können die Flags eines vorhandenen regulären Ausdrucks nicht ändern (ignoreCase usw. waren schon immer unveränderlich), aber flags ermöglicht es Ihnen, eine Kopie zu erstellen, bei der die Flags geändert wurden
function copyWithIgnoreCase(re) {
return new RegExp(re.source,
re.flags.includes('i') ? re.flags : re.flags+'i');
}
Der nächste Abschnitt erklärt eine weitere Möglichkeit, modifizierte Kopien von regulären Ausdrücken zu erstellen.
RegExp() kann als Kopierkonstruktor verwendet werden In ES6 gibt es zwei Varianten des Konstruktors RegExp() (die zweite ist neu)
new RegExp(pattern : string, flags = '')pattern angegeben. Wenn flags fehlt, wird der leere String '' verwendet.new RegExp(regex : RegExp, flags = regex.flags)regex wird geklont. Wenn flags angegeben ist, bestimmt es die Flags der Kopie.Die folgende Interaktion demonstriert die letztere Variante
> new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
Daher gibt uns der RegExp-Konstruktor eine weitere Möglichkeit, Flags zu ändern
function copyWithIgnoreCase(re) {
return new RegExp(re,
re.flags.includes('i') ? re.flags : re.flags+'i');
}
exec() Die folgende Funktion execAll() ist eine iterierbare Version von exec(), die mehrere Probleme mit der Verwendung von exec() zur Abfrage aller Übereinstimmungen eines regulären Ausdrucks behebt
exec() auf, bis es null zurückgibt).exec() mutiert den regulären Ausdruck, was bedeutet, dass Seiteneffekte zu einem Problem werden können./g muss gesetzt sein. Andernfalls wird nur die erste Übereinstimmung zurückgegeben.function* execAll(regex, str) {
// Make sure flag /g is set and regex.index isn’t changed
const localCopy = copyAndEnsureFlag(regex, 'g');
let match;
while (match = localCopy.exec(str)) {
yield match;
}
}
function copyAndEnsureFlag(re, flag) {
return new RegExp(re,
re.flags.includes(flag) ? re.flags : re.flags+flag);
}
Verwendung von execAll()
const str = '"fee" "fi" "fo" "fum"';
const regex = /"([^"]*)"/;
// Access capture of group #1 via destructuring
for (const [, group1] of execAll(regex, str)) {
console.log(group1);
}
// Output:
// fee
// fi
// fo
// fum
Die folgenden String-Methoden delegieren nun einen Teil ihrer Arbeit an reguläre Ausdrucksmethoden
String.prototype.match ruft RegExp.prototype[Symbol.match] auf.String.prototype.replace ruft RegExp.prototype[Symbol.replace] auf.String.prototype.search ruft RegExp.prototype[Symbol.search] auf.String.prototype.split ruft RegExp.prototype[Symbol.split] auf.Weitere Informationen finden Sie in Abschn. „String-Methoden, die reguläre Ausdrucksarbeit an ihre Parameter delegieren“ im Kapitel über Strings.