157

In the Chrome or Firebug console:

reg = /ab/g
str = "abc"
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null

Is exec somehow stateful and depends on what it returned the previous time? Or is this just a bug? I can't get it to happen all the time. For example, if 'str' above were "abc abc" it doesn't happen.

Sam Fen
  • 5,074
  • 5
  • 30
  • 56

3 Answers3

281

A JavaScript RegExp object is stateful.

When the regex is global, if you call a method on the same regex object, it will start from the index past the end of the last match.

When no more matches are found, the index is reset to 0 automatically.


To reset it manually, set the lastIndex property.

reg.lastIndex = 0;

This can be a very useful feature. You can start the evaluation at any point in the string if desired, or if in a loop, you can stop it after a desired number of matches.


Here's a demonstration of a typical approach to using the regex in a loop. It takes advantage of the fact that exec returns null when there are no more matches by performing the assignment as the loop condition.

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

while (match = re.exec(str))
    results.push(+match[1]);

DEMO: http://jsfiddle.net/pPW8Y/


If you don't like the placement of the assignment, the loop can be reworked, like this for example...

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

do {
    match = re.exec(str);
    if (match)
        results.push(+match[1]);
} while (match);

DEMO: http://jsfiddle.net/pPW8Y/1/

Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • 7
    You can also reset it with `reg.exec("")` because it only retains the state if you pass the same string – Esailija Jul 13 '12 at 19:57
  • It might be a good thing to include a `while` loop example, as this demonstrates the usefulness of this particular feature. – Tomalak Jul 13 '12 at 20:05
  • 5
    @Esailija @squint that's not entirely true. If you use the same regex to match against different strings, then the `lastIndex` remains set to where it was in the first string. I ran into this problem today. – grammar Jul 23 '15 at 18:53
  • @grammar: Not sure what you mean. [Here's a demo](http://jsfiddle.net/gqrt4mhL/1/) that uses the same regex on two different strings. On the second string it continues on from the `.lastIndex` given when searching the first string. The regex object is oblivious to the string it's searching. If that behavior is what you meant, then what do you mean by *"not entirely true"*. –  Jul 25 '15 at 15:41
  • 3
    ...oh, are you talking about Esailija's *"only retains state if you pass the same string"* comment? Yeah, that's not right. His technique resets it because it fails to find a match in an empty string. Maybe there's some case where it doesn't reset? I wouldn't use that technique anyway. –  Jul 25 '15 at 15:49
  • @squint yeah sorry I wasn't very clear. I meant if you use the same regex _object_ to test against multiple strings, then that instance will have the `lastIndex` set to wherever the match was in the first string, when you try to compare with the next. – grammar Jul 27 '15 at 19:38
  • @squint After re-reading, I realize that you already pointed out that the same regex instance will retain state when the `g` flag is used. This is where I was having trouble. – grammar Jul 27 '15 at 19:50
  • 1
    In case anyone has read this far, here's a fiddle to further explain my plight (which is explained by @squint's answer above): http://jsfiddle.net/grammar/k2hxsq8d/2/ – grammar Jul 27 '15 at 19:57
  • @grammar: Yeah, that's a common gotcha with these stateful regexp objects. If you want to do one search per string using the same regexp object, then you'll need to reset it before each search. You could also use `string.match(re)`, which should avoid the issue. –  Jul 27 '15 at 22:36
  • Sorry, but you can compress all of them to 1 line: `var result = 'text foo_123 more text foo_456 foo_789 end text'.match(/foo_(\d+)/g);` –  Nov 17 '15 at 06:25
  • @Mr.Wolf: I think you're missing the point. The point was to give a demonstration of how `.lastIndex` can be used. In other situations, a single `.match` call won't work, so `.lastIndex` can be useful. Also notice that you're getting the `foo_` with the numbers instead of just the numbers. –  Nov 17 '15 at 14:04
  • I did not know you can set the lastIndex. It is like using indexOf + fromIndex with regex. All I need to write a simple and fast parser. :-) – inf3rno Mar 01 '19 at 23:32
35

From MDN docs:

If your regular expression uses the "g" flag, you can use the exec method multiple times to find successive matches in the same string. When you do so, the search starts at the substring of str specified by the regular expression's lastIndex property (test will also advance the lastIndex property).

Since you are using the g flag, exec continues from the last matched string until it gets to the end (returns null), then starts over.


Personally, I prefer to go the other way around with str.match(reg)

Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
14

Multiple Matches

If your regex need the g flag (global match), you will need to reset the index (position of the last match) by using the lastIndex property.

reg.lastIndex = 0;

This is due to the fact that exec() will stop on each occurence so you can run again on the remaining part. This behavior also exists with test()) :

If your regular expression uses the "g" flag, you can use the exec method multiple times to find successive matches in the same string. When you do so, the search starts at the substring of str specified by the regular expression's lastIndex property (test will also advance the lastIndex property)

Single Match

When there is only one possible match, you can simply rewrite you regex by omitting the g flag, as the index will be automatically reset to 0.

Édouard Lopez
  • 40,270
  • 28
  • 126
  • 178