74

Why do Javascript sub-matches stop working when the g modifier is set?

var text = 'test test test test';

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

The above works fine, result[1] is "e" and result[2] is "s".

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

The above ignores my capturing groups. Is the following the only valid solution?

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"]
*/

EDIT:

I am back again to happily tell you that 10 years later you can now do this (.matchAll has been added to the spec)

let result = [...text.matchAll(/t(e)(s)t/g)];
5
  • 1
    Super nice with matchAll being able to do this :-)
    – marlar
    Commented Jun 16, 2022 at 9:07
  • Don't put the solution in the question, add an answer.
    – Barmar
    Commented May 22 at 21:21
  • @Barmar No need to get worked up over a 20-year-old post. Is there really nothing more current to moderate these days? This was from back when Stack Overflow was still genuinely helpful.
    – Chad
    Commented May 23 at 22:33
  • You edited it yesterday, which popped it near the top of the question list.
    – Barmar
    Commented May 23 at 22:35
  • I reverted a change where someone removed my original edit. I restored it because the top answer is incorrect, and that edit has helped thousands of people — as you can see from the revision history stackoverflow.com/revisions/844001/10 . I blame this guy: stackoverflow.com/revisions/844001/11 , wish I could steal a coffee from him.
    – Chad
    Commented May 23 at 22:37

2 Answers 2

101

Using String's match() function won't return captured groups if the global modifier is set, as you found out.

In this case, you would want to use a RegExp object and call its exec() function. String's match() is almost identical to RegExp's exec() function…except in cases like these. If the global modifier is set, the normal match() function won't return captured groups, while RegExp's exec() function will. (Noted here, among other places.)

Another catch to remember is that exec() doesn't return the matches in one big array—it keeps returning matches until it runs out, in which case it returns null.

So, for example, you could do something like this:

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...
}

Another thing to note is that RegExp.prototype.exec() and RegExp.prototype.test() execute the regular expression on the provided string and return the first result. Every sequential call will step through the result set updating RegExp.prototype.lastIndex based on the current position in the string.

Here's an example: // remember there are 4 matches in the example and pattern. lastIndex starts at 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);
}

You can find information on how to use RegExp objects on the MDN (specifically, here's the documentation for the exec() function).

11 Comments

Enter at least 15 characters
    Chad
using exec doesn't seem to listen to the g modifier, but it supports sub-matches/groups. So the result would be the first match (it basically ignores the g modifier)
Enter at least 15 characters
Enter at least 15 characters
    Chad
Not the most elegant solution. i was expecting an output somewhat like this: [ ["test", "e", "s"], ["test", "e", "s"], ["test", "e", "s"], ["test", "e", "s"] ]
Enter at least 15 characters
Enter at least 15 characters
Old, old question I know, but I had a need of this recently, and I whipped this up: RegExp.prototype.execAll = function(s) { var r = [],m; while(m = this.exec(s)) r.push(m); return r; }. With that, you can do: /t(e)(s)t/.matchAll("test") and get the results that @ChadScira was looking for.
Enter at least 15 characters
Enter at least 15 characters
Note for others bumping into another problem: If you use .test() before it, make sure you reset the lastIndex using pattern.lastIndex = 0 before the while loop to get all the matches
Enter at least 15 characters
Enter at least 15 characters
The g flag is not ignored. It needs to be there, otherwise you'll get an infinite loop. Found out the hard way here :)
Enter at least 15 characters
Enter at least 15 characters
|
Enter at least 15 characters
6

.matchAll has already been added to a few browsers.

In modern javascript we can now accomplish this by just doing the following.

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

.matchAll spec

.matchAll docs

I now maintain an isomorphic javascript library that helps with a lot of this type of string parsing. You can check it out here: string-saw. It assists in making .matchAll easier to use when using named capture groups.

An example would be

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

Which outputs a more user-friendly array of matches, and if you want to get fancy you can throw in named capture groups and get an array of objects.

2 Comments

Enter at least 15 characters
Languages evolve. So does Javascript. Glad to be the first upvoter of this gorgeous answer.
Enter at least 15 characters
Enter at least 15 characters
String#matchAll returns an iterator, not an array.
Enter at least 15 characters
Enter at least 15 characters
Enter at least 15 characters

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.