Kapitel 19. Reguläre Ausdrücke
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 19. Reguläre Ausdrücke

Dieses Kapitel gibt einen Überblick über die JavaScript-API für reguläre Ausdrücke. Es wird davon ausgegangen, dass Sie ungefähr wissen, wie sie funktionieren. Wenn nicht, gibt es viele gute Tutorials im Web. Zwei Beispiele sind:

Syntax regulärer Ausdrücke

Die hier verwendeten Begriffe spiegeln die Grammatik der ECMAScript-Spezifikation eng wider. Ich weiche manchmal ab, um Dinge leichter verständlich zu machen.

Atome: Allgemein

Die Syntax für allgemeine Atome lautet wie folgt:

Sonderzeichen

Alle folgenden Zeichen haben eine Sonderbedeutung:

\ ^ $ . * + ? ( ) [ ] { } |

Sie können sie durch Voranstellen eines Backslash maskieren. Zum Beispiel

> /^(ab)$/.test('(ab)')
false
> /^\(ab\)$/.test('(ab)')
true

Zusätzliche Sonderzeichen sind

  • Innerhalb einer Zeichenklasse [...]

    -
  • Innerhalb einer Gruppe, die mit einem Fragezeichen beginnt (?...)

    : = ! < >

    Die spitzen Klammern werden nur von der XRegExp-Bibliothek verwendet (siehe Kapitel 30), um Gruppen zu benennen.

Musterzeichen
Alle Zeichen außer den oben genannten Sonderzeichen stimmen mit sich selbst überein.
. (Punkt)

Passt zu jedem JavaScript-Zeichen (UTF-16-Codeeinheit) außer Zeilenumbrüchen (Neue Zeile, Wagenrücklauf usw.). Um wirklich jedes Zeichen abzugleichen, verwenden Sie [\s\S]. Zum Beispiel

> /./.test('\n')
false
> /[\s\S]/.test('\n')
true
Zeichenescapes (passen zu einzelnen Zeichen)
  • Spezifische Steuerzeichen sind \f (Seitenvorschub), \n (Zeilenvorschub, neue Zeile), \r (Wagenrücklauf), \t (horizontaler Tabulator) und \v (vertikaler Tabulator).
  • \0 passt zum NUL-Zeichen (\u0000).
  • Jedes Steuerzeichen: \cA\cZ.
  • Unicode-Zeichenescapes: \u0000\xFFFF (Unicode-Codeeinheiten; siehe Kapitel 24).
  • Hexadezimale Zeichenescapes: \x00\xFF.
Zeichenklassescapes (passen zu einem aus einer Menge von Zeichen)
  • Ziffern: \d passt zu jeder Ziffer (gleichbedeutend mit [0-9]); \D passt zu jeder Nicht-Ziffer (gleichbedeutend mit [^0-9]).
  • Alphanumerische Zeichen: \w passt zu jedem lateinischen alphanumerischen Zeichen plus Unterstrich (gleichbedeutend mit [A-Za-z0-9_]); \W passt zu allen Zeichen, die nicht von \w abgeglichen werden.
  • Leerzeichen: \s passt zu Leerzeichen (Leerzeichen, Tabulator, Zeilenvorschub, Wagenrücklauf, Seitenvorschub, alle Unicode-Leerzeichen usw.); \S passt zu allen Nicht-Leerzeichen.

Atome: Zeichenklassen

Die Syntax für Zeichenklassen lautet wie folgt:

  • [«charSpecs»] passt zu jedem einzelnen Zeichen, das mindestens einer der charSpecs entspricht.
  • [^«charSpecs»] passt zu jedem einzelnen Zeichen, das keiner der charSpecs entspricht.

Die folgenden Konstrukte sind alle Zeichen spezifikationen

  • Quellzeichen passen zu sich selbst. Die meisten Zeichen sind Quellzeichen (sogar viele Zeichen, die anderswo besonders sind). Nur drei Zeichen sind es nicht

        \ ] -

    Wie üblich maskieren Sie mit einem Backslash. Wenn Sie einen Bindestrich maskieren möchten, ohne ihn zu maskieren, muss er das erste Zeichen nach der öffnenden Klammer oder die rechte Seite eines Bereichs sein, wie kurz beschrieben.

  • Klassescapes: Jede der zuvor aufgeführten Zeichenescapes und Zeichenklassescapes ist zulässig. Es gibt eine zusätzliche Escape

    • Backspace (\b): Außerhalb einer Zeichenklasse passt \b zu Wortgrenzen. Innerhalb einer Zeichenklasse passt es zum Steuerzeichen *Backspace*.
  • Bereiche umfassen ein Quellzeichen oder einen Klassenscape, gefolgt von einem Bindestrich (-), gefolgt von einem Quellzeichen oder einem Klassenscape.

Um die Verwendung von Zeichenklassen zu demonstrieren, analysiert dieses Beispiel ein Datum im ISO 8601-Format

function parseIsoDate(str) {
    var match = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(str);

    // Other ways of writing the regular expression:
    // /^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$/
    // /^(\d\d\d\d)-(\d\d)-(\d\d)$/

    if (!match) {
        throw new Error('Not an ISO date: '+str);
    }
    console.log('Year: '  + match[1]);
    console.log('Month: ' + match[2]);
    console.log('Day: '   + match[3]);
}

Und hier ist die Interaktion

> parseIsoDate('2001-12-24')
Year: 2001
Month: 12
Day: 24

Atome: Gruppen

Die Syntax für Gruppen lautet wie folgt:

  • («pattern») ist eine erfassende Gruppe. Was auch immer von pattern abgeglichen wird, kann über Rückverweise oder als Ergebnis einer Abgleichoperation abgerufen werden.
  • (?:«pattern») ist eine nicht erfassende Gruppe. pattern wird immer noch gegen die Eingabe abgeglichen, aber nicht als Erfassung gespeichert. Daher hat die Gruppe keine Nummer, auf die Sie (z. B. über einen Rückverweis) verweisen können.

\1, \2 und so weiter werden als *Rückverweise* bezeichnet; sie verweisen auf eine zuvor abgeglichene Gruppe. Die Zahl nach dem Backslash kann jede ganze Zahl größer oder gleich 1 sein, aber die erste Ziffer darf nicht 0 sein.

In diesem Beispiel garantiert ein Rückverweis die gleiche Menge an a's vor und nach dem Bindestrich

> /^(a+)-\1$/.test('a-a')
true
> /^(a+)-\1$/.test('aaa-aaa')
true
> /^(a+)-\1$/.test('aa-a')
false

Dieses Beispiel verwendet einen Rückverweis, um ein HTML-Tag abzugleichen (offensichtlich sollten Sie normalerweise einen richtigen Parser zur Verarbeitung von HTML verwenden)

> var tagName = /<([^>]+)>[^<]*<\/\1>/;
> tagName.exec('<b>bold</b>')[1]
'b'
> tagName.exec('<strong>text</strong>')[1]
'strong'
> tagName.exec('<strong>text</stron>')
null

Quantifizierer

Jedes Atom (einschließlich Zeichenklassen und Gruppen) kann von einem Quantifizierer gefolgt werden:

  • ? bedeutet null oder einmal.
  • * bedeutet null oder mehrfach.
  • + bedeutet einmal oder mehrfach.
  • {n} bedeutet genau n Mal.
  • {n,} bedeutet n oder mehr Mal.
  • {n,m} bedeutet mindestens n, höchstens m Mal.

Standardmäßig sind Quantifizierer *gierig*; das heißt, sie passen so viel wie möglich. Sie können *zurückhaltendes* Abgleichen (so wenig wie möglich) erhalten, indem Sie jeden der vorherigen Quantifizierer (einschließlich der Bereiche in geschweiften Klammern) mit einem Fragezeichen (?) versehen. Zum Beispiel:

> '<a> <strong>'.match(/^<(.*)>/)[1]  // greedy
'a> <strong'
> '<a> <strong>'.match(/^<(.*?)>/)[1]  // reluctant
'a'

Daher ist .*? ein nützliches Muster, um alles bis zum nächsten Vorkommen des folgenden Atoms abzugleichen. Zum Beispiel ist das Folgende eine kompaktere Version des regulären Ausdrucks für HTML-Tags, der gerade gezeigt wurde (der [^<]* anstelle von .*? verwendete)

/<(.+?)>.*?<\/\1>/

Assertionen

Assertionen, die in der folgenden Liste aufgeführt sind, sind Prüfungen der aktuellen Position in der Eingabe:

^

Passt nur am Anfang der Eingabe.

$

Passt nur am Ende der Eingabe.

\b

Passt nur an einer Wortgrenze. Nicht verwechseln mit [\b], was einen Backspace abgleicht.

\B

Passt nur, wenn nicht an einer Wortgrenze.

(?=«pattern»)

Positive Lookahead: Passt nur, wenn pattern das Folgende abgleicht. pattern wird nur verwendet, um vorauszuschauen, aber ansonsten ignoriert.

(?!«pattern»)

Negative Lookahead: Passt nur, wenn pattern das Folgende nicht abgleicht. pattern wird nur verwendet, um vorauszuschauen, aber ansonsten ignoriert.

Dieses Beispiel passt zu einer Wortgrenze mit \b

> /\bell\b/.test('hello')
false
> /\bell\b/.test('ello')
false
> /\bell\b/.test('ell')
true

Dieses Beispiel passt zum Inneren eines Wortes mit \B

> /\Bell\B/.test('ell')
false
> /\Bell\B/.test('hell')
false
> /\Bell\B/.test('hello')
true

Hinweis

Lookbehind wird nicht unterstützt. Manuelles Implementieren von Lookbehind erklärt, wie es manuell implementiert werden kann.

Disjunktion

Ein Disjunktionsoperator (|) trennt zwei Alternativen; entweder die Alternativen müssen übereinstimmen, damit die Disjunktion übereinstimmt. Die Alternativen sind Atome (optional einschließlich Quantifizierern).

Der Operator bindet sehr schwach, sodass Sie vorsichtig sein müssen, dass die Alternativen nicht zu weit reichen. Zum Beispiel passt der folgende reguläre Ausdruck zu allen Zeichenketten, die entweder mit aa beginnen oder mit bb enden

> /^aa|bb$/.test('aaxx')
true
> /^aa|bb$/.test('xxbb')
true

Mit anderen Worten, die Disjunktion bindet schwächer als sogar ^ und $, und die beiden Alternativen sind ^aa und bb$. Wenn Sie die beiden Zeichenketten 'aa' und 'bb' abgleichen möchten, benötigen Sie Klammern

/^(aa|bb)$/

Ebenso, wenn Sie die Zeichenketten 'aab' und 'abb' abgleichen möchten

/^a(a|b)b$/

Unicode und reguläre Ausdrücke

JavaScript-Reguläre Ausdrücke haben nur sehr begrenzte Unterstützung für Unicode. Insbesondere wenn es um Code-Punkte in den Astralebenen geht, müssen Sie vorsichtig sein. Kapitel 24 erklärt die Details.

Erstellen eines regulären Ausdrucks

Sie können einen regulären Ausdruck entweder über ein Literal oder einen Konstruktor erstellen und konfigurieren, wie er über Flags funktioniert.

Literal vs. Konstruktor

Es gibt zwei Möglichkeiten, einen regulären Ausdruck zu erstellen: Sie können ein Literal oder den Konstruktor RegExp verwenden:

Literal

/xyz/i

Kompiliert beim Laden

Konstruktor (zweites Argument ist optional)

new RegExp('xyz', 'i')

Kompiliert zur Laufzeit

Ein Literal und ein Konstruktor unterscheiden sich darin, wann sie kompiliert werden

  • Das Literal wird beim Laden kompiliert. Der folgende Code löst eine Ausnahme aus, wenn er ausgewertet wird

    function foo() {
        /[/;
    }
  • Der Konstruktor kompiliert den regulären Ausdruck, wenn er aufgerufen wird. Der folgende Code löst keine Ausnahme aus, aber das Aufrufen von foo() wird dies tun:

    function foo() {
        new RegExp('[');
    }

Daher sollten Sie normalerweise Literale verwenden, aber Sie benötigen den Konstruktor, wenn Sie einen regulären Ausdruck dynamisch zusammensetzen möchten.

Flags

Flags sind ein Suffix von regulären Ausdrucksliteralen und ein Parameter von regulären Ausdruckskonstruktoren; sie modifizieren das Abgleichverhalten von regulären Ausdrücken. Es gibt die folgenden Flags:

KurznameLangnameBeschreibung

g

global

Der angegebene reguläre Ausdruck wird mehrmals abgeglichen. Beeinflusst mehrere Methoden, insbesondere replace().

i

ignoreCase

Groß-/Kleinschreibung wird beim Abgleichen des angegebenen regulären Ausdrucks ignoriert.

m

multiline

Im Multiline-Modus passen der Anfangsoperator ^ und der Endoperator $ zu jeder Zeile anstelle der vollständigen Eingabezeichenkette.

Der Kurzname wird für Literalpräfixe und Konstruktorparameter verwendet (siehe Beispiele im nächsten Abschnitt). Der Langname wird für Eigenschaften eines regulären Ausdrucks verwendet, die angeben, welche Flags während seiner Erstellung gesetzt wurden.

Instanzeigenschaften von regulären Ausdrücken

Reguläre Ausdrücke haben die folgenden Instanzeigenschaften:

  • Flags: boolesche Werte, die angeben, welche Flags gesetzt sind

    • global: Ist das Flag /g gesetzt?
    • ignoreCase: Ist das Flag /i gesetzt?
    • multiline: Ist das Flag /m gesetzt?
  • Daten für mehrfache Übereinstimmungen (Flag /g ist gesetzt)

    • lastIndex ist der Index, an dem die Suche fortgesetzt wird.

Das Folgende ist ein Beispiel für den Zugriff auf die Instanzeigenschaften für Flags

> var regex = /abc/i;
> regex.ignoreCase
true
> regex.multiline
false

Beispiele für die Erstellung regulärer Ausdrücke

In diesem Beispiel erstellen wir denselben regulären Ausdruck zuerst mit einem Literal und dann mit einem Konstruktor und verwenden die test()-Methode, um festzustellen, ob er mit einer Zeichenkette übereinstimmt:

> /abc/.test('ABC')
false
> new RegExp('abc').test('ABC')
false

In diesem Beispiel erstellen wir einen regulären Ausdruck, der die Groß-/Kleinschreibung ignoriert (Flag /i)

> /abc/i.test('ABC')
true
> new RegExp('abc', 'i').test('ABC')
true

RegExp.prototype.test: Gibt es eine Übereinstimmung?

Die test()-Methode prüft, ob ein regulärer Ausdruck regex mit einer Zeichenkette str übereinstimmt:

regex.test(str)

test() verhält sich je nachdem, ob das Flag /g gesetzt ist oder nicht, unterschiedlich.

Wenn das Flag /g nicht gesetzt ist, prüft die Methode, ob irgendwo in str eine Übereinstimmung besteht. Zum Beispiel

> var str = '_x_x';

> /x/.test(str)
true
> /a/.test(str)
false

Wenn das Flag /g gesetzt ist, gibt die Methode true so oft zurück, wie es Übereinstimmungen für regex in str gibt. Die Eigenschaft regex.lastIndex enthält den Index nach der letzten Übereinstimmung

> var regex = /x/g;
> regex.lastIndex
0

> regex.test(str)
true
> regex.lastIndex
2

> regex.test(str)
true
> regex.lastIndex
4

> regex.test(str)
false

String.prototype.search: An welchem Index gibt es eine Übereinstimmung?

Die search()-Methode sucht nach einer Übereinstimmung mit regex innerhalb von str:

str.search(regex)

Wenn eine Übereinstimmung besteht, wird der Index, an dem sie gefunden wurde, zurückgegeben. Andernfalls ist das Ergebnis -1. Die Eigenschaften global und lastIndex von regex werden ignoriert, da die Suche durchgeführt wird (und lastIndex nicht geändert wird).

Zum Beispiel

> 'abba'.search(/b/)
1
> 'abba'.search(/x/)
-1

Wenn das Argument von search() kein regulärer Ausdruck ist, wird es in einen solchen umgewandelt

> 'aaab'.search('^a+b+$')
0

RegExp.prototype.exec: Erfassende Gruppen

Der folgende Methodenaufruf erfasst Gruppen während des Abgleichs von regex mit str:

var matchData = regex.exec(str);

Wenn keine Übereinstimmung vorhanden war, ist matchData null. Andernfalls ist matchData ein *Ergebnis der Übereinstimmung*, ein Array mit zwei zusätzlichen Eigenschaften

Array-Elemente
  • Element 0 ist die Übereinstimmung für den vollständigen regulären Ausdruck (Gruppe 0, wenn Sie so wollen).
  • Element *n* > 1 ist die Erfassung von Gruppe *n*.
Eigenschaften
  • input ist die vollständige Eingabezeichenkette.
  • index ist der Index, an dem die Übereinstimmung gefunden wurde.

Erste Übereinstimmung (Flag /g nicht gesetzt)

Wenn das Flag /g nicht gesetzt ist, wird nur die erste Übereinstimmung zurückgegeben

> var regex = /a(b+)/;
> regex.exec('_abbb_ab_')
[ 'abbb',
  'bbb',
  index: 1,
  input: '_abbb_ab_' ]
> regex.lastIndex
0

Alle Übereinstimmungen (Flag /g gesetzt)

Wenn das Flag /g gesetzt ist, werden alle Übereinstimmungen zurückgegeben, wenn Sie exec() wiederholt aufrufen. Der Rückgabewert null signalisiert, dass keine weiteren Übereinstimmungen vorhanden sind. Die Eigenschaft lastIndex gibt an, wo die Übereinstimmung das nächste Mal fortgesetzt wird

> var regex = /a(b+)/g;
> var str = '_abbb_ab_';

> regex.exec(str)
[ 'abbb',
  'bbb',
  index: 1,
  input: '_abbb_ab_' ]
> regex.lastIndex
6

> regex.exec(str)
[ 'ab',
  'b',
  index: 7,
  input: '_abbb_ab_' ]
> regex.lastIndex
10

> regex.exec(str)
null

Hier schleifen wir über Übereinstimmungen

var regex = /a(b+)/g;
var str = '_abbb_ab_';
var match;
while (match = regex.exec(str)) {
    console.log(match[1]);
}

und wir erhalten die folgende Ausgabe

bbb
b

String.prototype.match: Erfassende Gruppen oder Rückgabe aller übereinstimmenden Teilzeichenketten

Der folgende Methodenaufruf gleicht regex mit str ab:

var matchData = str.match(regex);

Wenn das Flag /g von regex nicht gesetzt ist, verhält sich diese Methode wie RegExp.prototype.exec()

> 'abba'.match(/a/)
[ 'a', index: 0, input: 'abba' ]

Wenn das Flag gesetzt ist, gibt die Methode ein Array mit allen übereinstimmenden Teilzeichenketten in str zurück (d. h. Gruppe 0 jeder Übereinstimmung) oder null, wenn keine Übereinstimmung vorhanden ist

> 'abba'.match(/a/g)
[ 'a', 'a' ]
> 'abba'.match(/x/g)
null

String.prototype.replace: Suchen und Ersetzen

Die replace()-Methode durchsucht eine Zeichenkette str nach Übereinstimmungen mit search und ersetzt sie durch replacement:

str.replace(search, replacement)

Es gibt mehrere Möglichkeiten, wie die beiden Parameter angegeben werden können

search

Entweder eine Zeichenkette oder ein regulärer Ausdruck

  • Zeichenkette: Wird buchstäblich in der Eingabezeichenkette gesucht. Beachten Sie, dass nur das erste Vorkommen einer Zeichenkette ersetzt wird. Wenn Sie mehrere Vorkommen ersetzen möchten, müssen Sie einen regulären Ausdruck mit einem /g-Flag verwenden. Dies ist unerwartet und eine große Fallgrube.
  • Regulärer Ausdruck: Wird gegen die Eingabezeichenkette abgeglichen. Warnung: Verwenden Sie das global-Flag, da sonst nur ein Versuch unternommen wird, den regulären Ausdruck abzugleichen.
replacement

Entweder eine Zeichenkette oder eine Funktion

  • Zeichenkette: Beschreibt, wie das Gefundene ersetzt werden soll.
  • Funktion: Berechnet einen Ersatz und erhält Abgleichinformationen über Parameter.

Ersatz ist eine Zeichenkette

Wenn replacement eine Zeichenkette ist, wird ihr Inhalt wörtlich zum Ersetzen des Treffers verwendet. Die einzige Ausnahme ist das Sonderzeichen Dollarzeichen ($), das sogenannte *Ersetzungsdirektiven* einleitet:

  • Gruppen: $n fügt Gruppe n aus dem Treffer ein. n muss mindestens 1 sein ($0 hat keine besondere Bedeutung).
  • Der übereinstimmende Teilzeichenkette

    • $` (Backtick) fügt den Text vor dem Treffer ein.
    • $& fügt den vollständigen Treffer ein.
    • $' (Apostroph) fügt den Text nach dem Treffer ein.
  • $$ fügt ein einzelnes $ ein.

Dieses Beispiel bezieht sich auf die übereinstimmende Teilzeichenkette und ihren Präfix und Suffix

> 'axb cxd'.replace(/x/g, "[$`,$&,$']")
'a[a,x,b cxd]b c[axb c,x,d]d'

Dieses Beispiel bezieht sich auf eine Gruppe

> '"foo" and "bar"'.replace(/"(.*?)"/g, '#$1#')
'#foo# and #bar#'

Ersatz ist eine Funktion

Wenn replacement eine Funktion ist, berechnet sie die Zeichenkette, die den Treffer ersetzen soll. Diese Funktion hat die folgende Signatur:

function (completeMatch, group_1, ..., group_n, offset, inputStr)

completeMatch ist dasselbe wie $& zuvor, offset gibt an, wo der Treffer gefunden wurde, und inputStr ist das, womit abgeglichen wird. Daher können Sie die spezielle Variable arguments verwenden, um Gruppen abzurufen (Gruppe 1 über arguments[1] und so weiter). Zum Beispiel

> function replaceFunc(match) { return 2 * match }
> '3 apples and 5 oranges'.replace(/[0-9]+/g, replaceFunc)
'6 apples and 10 oranges'

Probleme mit dem Flag /g

Reguläre Ausdrücke, deren /g-Flag gesetzt ist, sind problematisch, wenn eine auf ihnen aufgerufene Methode mehrmals aufgerufen werden muss, um alle Ergebnisse zurückzugeben. Das ist bei zwei Methoden der Fall:

  • RegExp.prototype.test()
  • RegExp.prototype.exec()

Dann missbraucht JavaScript den regulären Ausdruck als Iterator, als Zeiger in die Ergebnisfolge. Das verursacht Probleme

Problem 1: /g-Reguläre Ausdrücke können nicht inline sein

Zum Beispiel

// Don’t do that:
var count = 0;
while (/a/g.test('babaa')) count++;

Die vorherige Schleife ist unendlich, da für jede Schleifeniteration ein neuer regulärer Ausdruck erstellt wird, der die Iteration über die Ergebnisse neu startet. Daher muss der Code umgeschrieben werden

var count = 0;
var regex = /a/g;
while (regex.test('babaa')) count++;

Hier ist ein weiteres Beispiel

// Don’t do that:
function extractQuoted(str) {
    var match;
    var result = [];
    while ((match = /"(.*?)"/g.exec(str)) != null) {
        result.push(match[1]);
    }
    return result;
}

Das Aufrufen der vorherigen Funktion führt erneut zu einer Endlosschleife. Die korrekte Version ist (warum lastIndex auf 0 gesetzt ist, wird kurz erklärt)

var QUOTE_REGEX = /"(.*?)"/g;
function extractQuoted(str) {
    QUOTE_REGEX.lastIndex = 0;
    var match;
    var result = [];
    while ((match = QUOTE_REGEX.exec(str)) != null) {
        result.push(match[1]);
    }
    return result;
}

Verwenden der Funktion

> extractQuoted('"hello", "world"')
[ 'hello', 'world' ]

Tipp

Es ist eine bewährte Methode, sowieso nicht inline zu sein (dann können Sie regulären Ausdrücken beschreibende Namen geben). Aber Sie müssen sich bewusst sein, dass Sie dies nicht tun können, nicht einmal bei schnellen Hacks.

Problem 2: /g-Reguläre Ausdrücke als Parameter
Code, der test() und exec() mehrmals aufrufen möchte, muss vorsichtig mit einem ihm als Parameter übergebenen regulären Ausdruck sein. Sein Flag /g muss aktiv sein und, um sicher zu sein, sollte sein lastIndex auf null gesetzt werden (eine Erklärung wird im nächsten Beispiel gegeben).
Problem 3: Geteilte /g-Reguläre Ausdrücke (z. B. Konstanten)
Immer wenn Sie sich auf einen regulären Ausdruck beziehen, der nicht neu erstellt wurde, sollten Sie seine lastIndex-Eigenschaft auf null setzen, bevor Sie ihn als Iterator verwenden (eine Erklärung wird im nächsten Beispiel gegeben). Da die Iteration von lastIndex abhängt, kann ein solcher regulärer Ausdruck nicht in mehr als einer Iteration gleichzeitig verwendet werden.

Das folgende Beispiel veranschaulicht Problem 2. Es ist eine naive Implementierung einer Funktion, die zählt, wie viele Übereinstimmungen es für den regulären Ausdruck regex in der Zeichenkette str gibt

// Naive implementation
function countOccurrences(regex, str) {
    var count = 0;
    while (regex.test(str)) count++;
    return count;
}

Hier ist ein Beispiel für die Verwendung dieser Funktion

> countOccurrences(/x/g, '_x_x')
2

Das erste Problem ist, dass diese Funktion in eine Endlosschleife gerät, wenn das /g-Flag des regulären Ausdrucks nicht gesetzt ist. Zum Beispiel

countOccurrences(/x/, '_x_x') // never terminates

Das zweite Problem ist, dass die Funktion nicht korrekt funktioniert, wenn regex.lastIndex nicht 0 ist, da diese Eigenschaft angibt, wo die Suche beginnen soll. Zum Beispiel

> var regex = /x/g;
> regex.lastIndex = 2;
> countOccurrences(regex, '_x_x')
1

Die folgende Implementierung behebt die beiden Probleme

function countOccurrences(regex, str) {
    if (! regex.global) {
        throw new Error('Please set flag /g of regex');
    }
    var origLastIndex = regex.lastIndex;  // store
    regex.lastIndex = 0;

    var count = 0;
    while (regex.test(str)) count++;

    regex.lastIndex = origLastIndex;  // restore
    return count;
}

Eine einfachere Alternative ist die Verwendung von match()

function countOccurrences(regex, str) {
    if (! regex.global) {
        throw new Error('Please set flag /g of regex');
    }
    return (str.match(regex) || []).length;
}

Es gibt eine mögliche Fallgrube: str.match() gibt null zurück, wenn das /g-Flag gesetzt ist und keine Übereinstimmungen gefunden werden. Wir vermeiden diese Fallgrube im obigen Code, indem wir [] verwenden, wenn das Ergebnis von match() nicht wahrheitsgemäß ist.

Tipps und Tricks

Dieser Abschnitt gibt einige Tipps und Tricks für die Arbeit mit regulären Ausdrücken in JavaScript.

Text zitieren

Manchmal, wenn Sie manuell einen regulären Ausdruck zusammensetzen, möchten Sie eine gegebene Zeichenkette wörtlich verwenden. Das bedeutet, dass keines der Sonderzeichen (z. B. *, [) als solches interpretiert werden sollte - alle müssen maskiert werden. JavaScript hat keine integrierten Mittel für diese Art von Zitieren, aber Sie können Ihre eigene Funktion, quoteText, programmieren, die wie folgt funktioniert:

> console.log(quoteText('*All* (most?) aspects.'))
\*All\* \(most\?\) aspects\.

Eine solche Funktion ist besonders nützlich, wenn Sie eine Suche und Ersetzung mit mehreren Vorkommen durchführen müssen. Dann muss der zu suchende Wert ein regulärer Ausdruck mit gesetztem global-Flag sein. Mit quoteText() können Sie beliebige Zeichenketten verwenden. Die Funktion sieht so aus

function quoteText(text) {
    return text.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&');
}

Alle Sonderzeichen werden maskiert, da Sie möglicherweise mehrere Zeichen innerhalb von Klammern oder eckigen Klammern maskieren möchten.

Fallgrube: Ohne eine Assertion (z. B. ^, $), wird ein regulärer Ausdruck überall gefunden

Wenn Sie keine Assertionen wie ^ und $ verwenden, finden die meisten Methoden für reguläre Ausdrücke ein Muster überall. Zum Beispiel:

> /aa/.test('xaay')
true
> /^aa$/.test('xaay')
false

Alles oder nichts abgleichen

Es ist ein seltener Anwendungsfall, aber manchmal benötigen Sie einen regulären Ausdruck, der alles oder nichts abgleicht. Zum Beispiel kann eine Funktion einen Parameter mit einem regulären Ausdruck haben, der zum Filtern verwendet wird. Wenn dieser Parameter fehlt, geben Sie ihm einen Standardwert, einen regulären Ausdruck, der alles abgleicht.

Alles abgleichen

Der leere reguläre Ausdruck passt zu allem. Wir können eine Instanz von RegExp basierend auf diesem regulären Ausdruck wie folgt erstellen

> new RegExp('').test('dfadsfdsa')
true
> new RegExp('').test('')
true

Der leere reguläre Ausdruck literal wäre jedoch //, was von JavaScript als Kommentar interpretiert wird. Daher ist das Folgende das Nächstliegende, das Sie über ein Literal erhalten können: /(?:)/ (leere nicht erfassende Gruppe). Die Gruppe passt zu allem, ohne etwas zu erfassen, was die Gruppe vom Einfluss auf das von exec() zurückgegebene Ergebnis fernhält. Sogar JavaScript selbst verwendet die vorherige Darstellung, wenn es einen leeren regulären Ausdruck anzeigt

> new RegExp('')
/(?:)/

Nichts abgleichen

Der leere reguläre Ausdruck hat eine Umkehrung - den regulären Ausdruck, der nichts abgleicht

> var never = /.^/;
> never.test('abc')
false
> never.test('')
false

Manuelles Implementieren von Lookbehind

Lookbehind ist eine Assertion. Ähnlich wie bei Lookahead wird ein Muster verwendet, um etwas über die aktuelle Position in der Eingabe zu prüfen, aber ansonsten ignoriert. Im Gegensatz zu Lookahead muss der Treffer des Musters *enden* an der aktuellen Position (nicht dort beginnen).

Die folgende Funktion ersetzt jedes Vorkommen der Zeichenkette 'NAME' durch den Wert des Parameters name, aber nur, wenn das Vorkommen nicht von einem Anführungszeichen preceded wird. Wir behandeln das Anführungszeichen, indem wir das Zeichen vor dem aktuellen Treffer "manuell" prüfen

function insertName(str, name) {
    return str.replace(
        /NAME/g,
        function (completeMatch, offset) {
            if (offset === 0 ||
                (offset > 0 && str[offset-1] !== '"')) {
                return name;
            } else {
                return completeMatch;
            }
        }
    );
}
> insertName('NAME "NAME"', 'Jane')
'Jane "NAME"'
> insertName('"NAME" NAME', 'Jane')
'"NAME" Jane'

Eine Alternative ist, die Zeichen einzuschließen, die maskieren können, in den regulären Ausdruck. Dann müssen Sie der Zeichenkette, in der Sie suchen, vorübergehend ein Präfix hinzufügen; andernfalls verpassen Sie Treffer am Anfang dieser Zeichenkette

function insertName(str, name) {
    var tmpPrefix = ' ';
    str = tmpPrefix + str;
    str = str.replace(
        /([^"])NAME/g,
        function (completeMatch, prefix) {
            return prefix + name;
        }
    );
    return str.slice(tmpPrefix.length); // remove tmpPrefix
}

Spickzettel für reguläre Ausdrücke

Atome (siehe Atome: Allgemein)

  • . (Punkt) gleicht alles außer Zeilenumbrüchen (z. B. neue Zeilen) ab. Verwenden Sie [\s\S], um wirklich alles abzugleichen.
  • Zeichenklassescapes

    • \d passt zu Ziffern ([0-9]); \D passt zu Nicht-Ziffern ([^0-9]).
    • \w passt zu lateinischen alphanumerischen Zeichen plus Unterstrich ([A-Za-z0-9_]); \W passt zu allen anderen Zeichen.
    • \s passt zu allen Leerzeichen (Leerzeichen, Tabulator, Zeilenvorschub usw.); \S passt zu allen Nicht-Leerzeichen.
  • Zeichenklasse (Zeichensatz): [...] und [^...]

    • Quellzeichen: [abc] (alle Zeichen außer \ ] - stimmen mit sich selbst überein)
    • Zeichenklassescapes (siehe vorherige): [\d\w]
    • Bereiche: [A-Za-z0-9]
  • Gruppen

    • Erfassende Gruppe: (...); Rückverweis: \1
    • Nicht erfassende Gruppe: (?:...)

Quantifizierer (siehe Quantifizierer)

  • Gierig

    • ? * +
    • {n} {n,} {n,m}
  • Zurückhaltend: Setzen Sie ein ? nach jedem der gierigen Quantifizierer.

Assertionen (siehe Assertionen)

  • Anfang der Eingabe, Ende der Eingabe: ^ $
  • An einer Wortgrenze, nicht an einer Wortgrenze: \b \B
  • Positive Lookahead: (?=...) (Muster muss folgen, wird aber ansonsten ignoriert)
  • Negative Lookahead: (?!...) (Muster darf nicht folgen, wird aber ansonsten ignoriert)

Disjunktion: |

Erstellen eines regulären Ausdrucks (siehe Erstellen eines regulären Ausdrucks)

  • Literal: /xyz/i (beim Laden kompiliert)
  • Konstruktor: new RegExp('xzy', 'i') (zur Laufzeit kompiliert)

Flags (siehe Flags)

  • global: /g (beeinflusst mehrere Methoden für reguläre Ausdrücke)
  • ignoreCase: /i
  • multiline: /m (^ und $ passen pro Zeile, im Gegensatz zur vollständigen Eingabe)

Methoden

Tipps zur Verwendung des Flags /g finden Sie unter Probleme mit dem Flag /g.

Danksagungen

Mathias Bynens (@mathias) und Juan Ignacio Dopazo (@juandopazo) empfahlen die Verwendung von match() und test() zum Zählen von Vorkommen, und Šime Vidas (@simevidas) warnte mich davor, vorsichtig mit match() zu sein, wenn es keine Treffer gibt. Die Fallstricke des globalen Flags, das zu Endlosschleifen führt, stammen aus einem Vortrag von Andrea Giammarchi (@webreflection). Claude Pache riet mir, in quoteText() mehr Zeichen zu escapen.

Weiter: 20. Daten