Regelmäßige JavaScript-Ausdrücke und Unterübereinstimmungen

70

Warum funktionieren Javascript-Unterübereinstimmungen nicht mehr, wenn der gModifikator festgelegt ist?

var text = 'test test test test';

var result = text.match(/t(e)(s)t/);
// Result: ["test", "e", "s"]

Das obige funktioniert gut, result[1]ist "e"und result[2]ist "s".

var result = text.match(/t(e)(s)t/g);
// Result: ["test", "test", "test", "test"]

Das Obige ignoriert meine Erfassungsgruppen. Ist das Folgende die einzig gültige Lösung?

var result = text.match(/test/g);
for (var i in result) {
    console.log(result[i].match(/t(e)(s)t/));
}
/* Result:
["test", "e", "s"]
["test", "e", "s"]
["test", "e", "s"]
["test", "e", "s"]
*/

BEARBEITEN:

Ich bin wieder zurück, um Ihnen gerne zu sagen, dass Sie dies 10 Jahre später jetzt tun können (.matchAll wurde der Spezifikation hinzugefügt).

let result = [...text.matchAll(/t(e)(s)t/g)];
Chad Scira
quelle

Antworten:

96

Mit String‚s match()Funktion gibt Gruppen nicht erfasst , wenn die globale Modifikator gesetzt ist, wie Sie herausgefunden haben.

In diesem Fall möchten Sie ein RegExpObjekt verwenden und dessen exec()Funktion aufrufen . String's match()ist fast identisch mit RegExp' s exec()Funktion ... außer in solchen Fällen. Wenn der globale Modifikator festgelegt ist, gibt die normale match()Funktion keine erfassten Gruppen zurück, während RegExpdie exec()Funktion dies tut. ( Hier unter anderem notiert.)

Ein weiterer wichtiger Punkt ist, dass exec()die Übereinstimmungen nicht in einem großen Array zurückgegeben werden. Es werden weiterhin Übereinstimmungen zurückgegeben, bis sie abgelaufen sind. In diesem Fall werden sie zurückgegeben null.

So könnten Sie beispielsweise Folgendes tun:

var pattern = /t(e)(s)t/g;  // Alternatively, "new RegExp('t(e)(s)t', 'g');"
var match;    

while (match = pattern.exec(text)) {
    // Do something with the match (["test", "e", "s"]) here...
}

Eine andere Sache zu beachten ist , dass RegExp.prototype.exec()und RegExp.prototype.test()den regulären Ausdruck auf der mitgelieferten String auszuführen und das erste Ergebnis zurück. Bei jedem sequentiellen Aufruf wird die Aktualisierung der Ergebnismenge RegExp.prototype.lastIndexbasierend auf der aktuellen Position in der Zeichenfolge schrittweise durchgeführt .

Hier ist ein Beispiel: // Denken Sie daran, dass das Beispiel und das Muster 4 Übereinstimmungen enthalten. lastIndex beginnt bei 0

pattern.test(text); // pattern.lastIndex = 4
pattern.test(text); // pattern.lastIndex = 9
pattern.exec(text); // pattern.lastIndex = 14
pattern.exec(text); // pattern.lastIndex = 19

// if we were to call pattern.exec(text) again it would return null and reset the pattern.lastIndex to 0
while (var match = pattern.exec(text)) {
    // never gets run because we already traversed the string
    console.log(match);
}

pattern.test(text); // pattern.lastIndex = 4
pattern.test(text); // pattern.lastIndex = 9

// however we can reset the lastIndex and it will give us the ability to traverse the string from the start again or any specific position in the string
pattern.lastIndex = 0;

while (var match = pattern.exec(text)) {
    // outputs all matches
    console.log(match);
}

Sie finden Informationen zur Verwendung von RegExpObjekten im MDN (insbesondere hier die Dokumentation für die exec()Funktion ).

hbw
quelle
3
Die Verwendung von exec scheint nicht auf den Modifikator g zu hören, unterstützt jedoch Unterübereinstimmungen / Gruppen. Das Ergebnis wäre also das erste Match (es ignoriert im Grunde den Modifikator g)
Chad Scira
2
Nicht die eleganteste Lösung. Ich hatte eine Ausgabe wie diese erwartet: [["Test", "e", "s"], ["Test", "e", "s"], ["Test", "e", "s" ], ["test", "e", "s"]]
Chad Scira
3
Hinweis für andere, die auf ein anderes Problem stoßen: Wenn Sie es .test()zuvor verwenden, stellen Sie sicher, dass Sie den lastIndex pattern.lastIndex = 0vor der whileSchleife zurücksetzen , um alle Übereinstimmungen zu erhalten
Iulian Onofrei
2
Das g-Flag wird nicht ignoriert. Es muss da sein, sonst bekommst du eine Endlosschleife. Hier auf die harte
Sarsaparilla
1
Explizite Adressierung eines anderen Eckfalls: Wenn der reguläre Ausdruck eine Erfassungsgruppe hat, aber kein globaler Modifikator verwendet wird, gibt match () zuerst die vollständige Übereinstimmung zurück , dann alle Teilzeichenfolgen, die mit der Erfassung übereinstimmen. ZB 'foobar'.match(/f(o)*(ba)/)wird zurückkehren ["fooba", "o", "ba"].
Eric Nguyen
4

Ich bin überrascht zu sehen, dass ich die erste Person bin, die diese Frage mit der Antwort beantwortet, nach der ich vor 10 Jahren gesucht habe (die Antwort gab es noch nicht). Ich hatte auch gehofft, dass die eigentlichen Spezifikationsschreiber es vor mir beantwortet hätten;).

.matchAll wurde bereits einigen Browsern hinzugefügt.

Im modernen Javascript können wir dies jetzt erreichen, indem wir einfach Folgendes tun.

let result = [...text.matchAll(/t(e)(s)t/g)];

.matchAll spec

.matchAll docs

Ich verwalte jetzt eine isomorphe Javascript-Bibliothek, die bei vielen dieser Arten der String-Analyse hilft. Sie können es hier überprüfen: Saitensäge . Es hilft dabei, die Verwendung von .matchAll zu vereinfachen, wenn benannte Erfassungsgruppen verwendet werden.

Ein Beispiel wäre

saw(text).matchAll(/t(e)(s)t/g)

Dies gibt eine benutzerfreundlichere Reihe von Übereinstimmungen aus, und wenn Sie Lust haben, können Sie benannte Erfassungsgruppen einwerfen und eine Reihe von Objekten erhalten.

Chad Scira
quelle
1
Sprachen entwickeln sich. Das Gleiche gilt für Javascript . Ich bin froh, der erste Befürworter dieser großartigen Antwort zu sein.
20онстантин Ван