views:

548

answers:

4

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]
+5  A: 

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));
Roatin Marth
This pretty much explains it all. Thanks!
Mark Raddatz
A: 

This is because first test matches with "Foo B", and second test not matched with "ar".

Anatoliy
+3  A: 

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'));
J-P
+6  A: 

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'));

As far as I know, only Firefox 3+, allows you to specify the sticky flag (y), which will reset the counter after every match. Alternatively, you could manually reset it.

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.
Ionuț G. Stan
I see. But what is the reason of keeping track of the lastIndex when I use a totally new string? Should it not auto-reset to 0?
Mark Raddatz
That's what the spec dictates. So, it's a design choice. It's all in section 15.10.6.2 of the ECMAScript 3 specification.
Ionuț G. Stan
The reference to the specification is a good hint!
Mark Raddatz