9

I create a RegExp object (in JavaScript) to test for the presence of a number:

var test = new RegExp( '[0-9]', 'g' );

I use it like this

console.log( test.test( '0' ) ); // true
console.log( test.test( '1' ) ); // false - why?

The output of this is even more confusing:

console.log( test.test( '1' ) ); // true
console.log( test.test( '0' ) ); // false - why?
console.log( test.test( '1' ) ); // true
console.log( test.test( '2' ) ); // false - why?
console.log( test.test( '2' ) ); // true - correct, but why is this one true?

If I remove the g qualifier, it behaves as expected.

Is this a bug as I believe it is, or some peculiar part of the spec? Is the g qualifier supposed to be used this way? (I'm re-using the same expression for multiple tasks, hence having the qualifier at all)

suDocker
  • 8,504
  • 6
  • 26
  • 26
Dave
  • 44,275
  • 12
  • 65
  • 105

3 Answers3

7

Per documentation: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/test#Description

test called multiple times on the same global regular expression instance will advance past the previous match.

You can confirm this behavior:

var test = new RegExp( '[0-9]', 'g' );
test.test('01'); //true
test.test('01'); //true
test.test('01'); //false

It doesn't make sense to use the g flag if all you want is to confirm a single match against various strings.

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
  • Thanks. You were a couple of minutes behind jfriend00 but this seems like a more complete answer. Just waiting for the countdown to expire to accept! – Dave Mar 21 '13 at 23:26
6

Remove the 'g' flag. When you use the 'g' flag, it updates the lastIndex property of the regex (preparing for a successive search on the same string) and then starts the next search from that index value (thus giving you a false reading on your next search).

Similar question and answer here: Why is Regex Javascript //g flag affecting state?

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Do you mean it ignores the input string the second time I call it, and continues to look for matches in the previously-given string? That's pretty weird – Dave Mar 21 '13 at 23:19
  • @Dave. It's updating the `lastIndex` property in the regex object and starting the second match from there which gives you the false reading. – jfriend00 Mar 21 '13 at 23:19
  • Ah ok, I think I understand now. I put a quick test together and it seems it uses the new string, but searches from the last matched index: http://jsfiddle.net/SnxSg/ This has got to be the weirdest behaviour ever, but at least I understand now. Thanks! – Dave Mar 21 '13 at 23:21
1

According to MDN,

As with exec (or in combination with it), test called multiple times on the same global regular expression instance will advance past the previous match.

Technically, the ECMAScript 5.1 spec says

15.10.6.3 RegExp.prototype.test(string)

The following steps are taken:

  1. Let match be the result of evaluating the RegExp.prototype.exec (15.10.6.2) algorithm upon this RegExp object using string as the argument.
  2. If match is not null, then return true; else return false.

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 string did not match.

The String ToString(string) is searched for an occurrence of the regular expression pattern as follows:

  1. Let R be this RegExp object.
  2. [...]
  3. [...]
  4. Let lastIndex be the result of calling the [[Get]] internal method of R with argument "lastIndex".
  5. Let i be the value of ToInteger(lastIndex).
  6. Let global be the result of calling the [[Get]] internal method of R with argument "global".
  7. If global is false, then let i = 0.
  8. [...]
  9. [...]
  10. Let e be r's endIndex value.
  11. If global is true,
    1. Call the [[Put]] internal method of R with arguments "lastIndex", e, and true.
  12. [...]

Therefore, to avoid this behavior, you can

  • Avoid using global flag g

    This way, at step 7, i will be 0 instead of lastIndex.

  • Reset lastIndex manually after each use

    The value of the lastIndex property specifies the String position at which to start the next match.

    For example,

    var test = /[0-9]/g;
    test.test('0');      // true
    test.lastIndex;      // 1
    test.lastIndex = 0;
    test.test('1');      // true
    
  • Use match or search string methods

    match resets lastIndex to 0, and search ignores it:

    15.5.4.10 String.prototype.match (regexp)

    [...] [If] global is true, call the [[Put]] internal method of rx with arguments "lastIndex" and 0. [...]

    15.5.4.12 String.prototype.search (regexp)

    [...] Search the value string from its beginning for an occurrence of the regular expression pattern rx. [...] The lastIndex and global properties of regexp are ignored when performing the search. [...]

    For example,

    var test = /[0-9]/g;
    test.test('0');        // true
    test.lastIndex;        // 1
    '0'.search(test) > -1; // true
    test.lastIndex;        // 1 (unaltered)
    !!'0'.match(test);     // true
    test.lastIndex;        // 0
    
Oriol
  • 274,082
  • 63
  • 437
  • 513