By about


2009-10-05 15:32:00 8 Comments

What is the problem with this regular expression when I use the global flag and the case insensitive flag? Query is a user generated input. The result should be [true, true].

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));

6 comments

@Ionuț G. Stan 2009-10-05 15:42:21

The RegExp object keeps track of the lastIndex where a match occurred, so on subsequent matches it will start from the last used index, instead of 0. Take a look:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

If you don't want to manually reset lastIndex to 0 after every test, just remove the g flag.

Here's the algorithm that the specs dictate (section 15.10.6.2):

RegExp.prototype.exec(string)

Performs a regular expression match of string against the regular expression and returns an Array object containing the results of the match, or null if the string did not match The string ToString(string) is searched for an occurrence of the regular expression pattern as follows:

  1. Let S be the value of ToString(string).
  2. Let length be the length of S.
  3. Let lastIndex be the value of the lastIndex property.
  4. Let i be the value of ToInteger(lastIndex).
  5. If the global property is false, let i = 0.
  6. If I < 0 or I > length then set lastIndex to 0 and return null.
  7. Call [[Match]], giving it the arguments S and i. If [[Match]] returned failure, go to step 8; otherwise let r be its State result and go to step 10.
  8. Let i = i+1.
  9. Go to step 6.
  10. Let e be r's endIndex value.
  11. If the global property is true, set lastIndex to e.
  12. Let n be the length of r's captures array. (This is the same value as 15.10.2.1's NCapturingParens.)
  13. Return a new array with the following properties:
    • The index property is set to the position of the matched substring within the complete string S.
    • The input property is set to S.
    • The length property is set to n + 1.
    • The 0 property is set to the matched substring (i.e. the portion of S between offset i inclusive and offset e exclusive).
    • For each integer i such that I > 0 and I ≤ n, set the property named ToString(i) to the ith element of r's captures array.

@Retsam 2013-08-22 19:54:56

This is like Hitchhiker's Guide to the Galaxy API design here. "That pitfall that you fell in has been perfectly documented in the spec for several years, if you had only bothered to check"

@Doin 2014-01-14 12:15:53

Firefox's sticky flag doesn't do what you imply at all. Rather, it acts as if there were a ^ at the start of the regular expression, EXCEPT that this ^ matches the current string position (lastIndex) rather than the start of the string. You're effectively testing if the regex matches "right here" instead of "anywhere after lastIndex". See the link you provided!

@Prestaul 2014-08-28 18:38:25

The opening statement of this answer is just not accurate. You highlighted step 3 of the spec which says nothing. The actual influence of lastIndex is in steps 5, 6 and 11. Your opening statement is only true IF THE GLOBAL FLAG IS SET.

@Ionuț G. Stan 2014-08-29 00:45:00

@Prestaul yes, you're right that it doesn't mention the global flag. It was probably (can't remember what I thought back then) implicit due to the way the question is framed. Feel free to edit the answer or delete it and link to your answer. Also, let me reassure you that you're better than me. Enjoy!

@Prestaul 2014-08-29 22:11:59

@IonuțG.Stan, sorry if my previous comment seemed attacky, that was not my intent. I can't edit it at this point, but I wasn't trying to shout, just to draw attention to the essential point of my comment. My bad!

@Chelmite 2018-06-28 23:05:25

I had the function:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

The first call works. The second call doesn't. The slice operation complains about a null value. I assume this is because of the re.lastIndex. This is strange because I would expect a new RegExp to be allocated each time the function is called and not shared across multiple invocations of my function.

When I changed it to:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

Then I don't get the lastIndex holdover effect. It works as I would expect it to.

@Scott Schlechtleitner 2017-09-21 00:33:22

Using the /g flag tells it to continue searching after a hit.

If the match succeeds, the exec() method returns an array and updates properties of the regular expression object.

Before your first search:

myRegex.lastIndex
//is 0

After the first search

myRegex.lastIndex
//is 8

Remove the g and it exits the search after each call to exec().

@user2572074 2013-11-12 21:34:46

Removing global g flag will fix your problem.

var re = new RegExp(query, 'gi');

Should be

var re = new RegExp(query, 'i');

@James 2009-10-05 15:41:01

RegExp.prototype.test updates the regular expressions' lastIndex property so that each test will start where the last one stopped. I'd suggest using String.prototype.match since it doesn't update the lastIndex property:

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

Note: !! converts it to a boolean and then inverts the boolean so it reflects the result.

Alternatively, you could just reset the lastIndex property:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));

@Roatin Marth 2009-10-05 15:40:02

You are using a single RegExp object and executing it multiple times. On each successive execution it continues on from the last match index.

You need to "reset" the regex to start from the beginning before each execution:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

Having said that it may be more readable to create a new RegExp object each time (overhead is minimal as the RegExp is cached anyway):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

Related Questions

Sponsored Content

2 Answered Questions

[SOLVED] How do I remove the global flag from a regular expression?

  • 2015-08-13 21:15:00
  • trex005
  • 620 View
  • 1 Score
  • 2 Answer
  • Tags:   javascript regex

5 Answered Questions

[SOLVED] Regular expression for exact match of a string

  • 2011-04-22 06:24:41
  • Chirayu
  • 436963 View
  • 110 Score
  • 5 Answer
  • Tags:   regex

10 Answered Questions

[SOLVED] Converting user input string to regular expression

  • 2009-05-17 14:20:18
  • Gordon Gustafson
  • 243275 View
  • 304 Score
  • 10 Answer
  • Tags:   javascript html regex

6 Answered Questions

[SOLVED] Why does Google prepend while(1); to their JSON responses?

9 Answered Questions

[SOLVED] Why does ++[[]][+[]]+[+[]] return the string "10"?

  • 2011-08-26 08:46:14
  • JohnJohnGa
  • 192374 View
  • 1571 Score
  • 9 Answer
  • Tags:   javascript syntax

11 Answered Questions

[SOLVED] Why does Date.parse give incorrect results?

  • 2010-04-06 18:35:53
  • user121196
  • 416141 View
  • 317 Score
  • 11 Answer
  • Tags:   javascript date

4 Answered Questions

[SOLVED] Changing the RegExp flags

1 Answered Questions

[SOLVED] Escape string for use in Javascript regex

1 Answered Questions

Regexp: case different flags for different branches

  • 2017-10-26 17:29:21
  • dvsoukup
  • 29 View
  • 0 Score
  • 1 Answer
  • Tags:   javascript regex

3 Answered Questions

[SOLVED] Why does the same RegExp behave differently?

  • 2012-06-13 19:25:45
  • Joe
  • 307 View
  • 12 Score
  • 3 Answer
  • Tags:   javascript regex

Sponsored Content