9

Consider:

var reg = new RegExp("^19[-\\d]*","g");
reg.test('1973-02-01') // true
reg.test('1973-01-01') // false

Why does the third line return false? If I remove the "g" flag, then it returns true.

Scimonster
  • 32,893
  • 9
  • 77
  • 89
Chopper Lee
  • 1,557
  • 2
  • 11
  • 30

1 Answers1

18

In JavaScript, regular expression objects have state. This matters when the g flag ("global") is applied to them, and sometimes applies in odd ways. This state is the index where the match last occurred, which is the regex's .lastIndex property. When you call exec or test on the same regex object again, it picks up from where it left off.

What's happening in your example is for the second call, it's picking up where it left off the last time, and so it's looking starting after the 10th character in the string — and doesn't find a match there, because there's no text there at all (and even if there were, the ^ assertion wouldn't match).

We can see what's happening if we look at the lastIndex property:

var reg = new RegExp("^19[-\\d]*","g");
snippet.log("Before first test: " + reg.lastIndex);
snippet.log(reg.test('1973-02-01')); //return true
snippet.log("Before second test: " + reg.lastIndex);
snippet.log(reg.test('1973-01-01')); //return false
snippet.log("After second test: " + reg.lastIndex);
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Without the g flag, the regex object doesn't keep any state and starts from the beginning of the string each time:

var reg = new RegExp("^19[-\\d]*");
snippet.log("Before first test: " + reg.lastIndex);
snippet.log(reg.test('1973-02-01')); //return true
snippet.log("Before second test: " + reg.lastIndex);
snippet.log(reg.test('1973-01-01')); //return false
snippet.log("After second test: " + reg.lastIndex);
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Side note: In general, it's best to write regular expressions in JvaScript using regex literals rather than using the RegExp constructor and strings. In your case, that would be

var reg = /^19[-\d]*/g;
// or without the g flag:
var reg = /^19[-\d]*/;

Side note 2: It doesn't make much sense to define a regular expression with a ^ or $ anchor and the g flag unless you're also using the m (multiline) flag to change what those anchors mean. without m, they mean "beginning (^) or end ($) of input." With the m flag, they mean "beginning (^) or end ($) of line."

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 4
    A good and comperhensive answer (as usual :-). I would add a note (3) that clarifies that the exact same output would be generated if the 2 date strings were identical (for example, `'1973-02-01'` in both calls) – Amit Sep 06 '15 at 08:55
  • thanks for your explain, it helps a lot. And about your side note, i think it is ` var reg = [^19[-\d]*/g;` – Chopper Lee Sep 06 '15 at 08:59
  • @ChopperLee: Yeah, I forgot to remove the backslash. :-) I remembered when I *started* typing the side note, but... (Fixed now) – T.J. Crowder Sep 06 '15 at 09:01
  • I think you should explain why the use of a regex literal fixes this. At first glance, you're just using a different notation for constructing then storing a `RegExp` object, and reusing `reg` here I'd expect the same behaviour as in the question. – Lightness Races in Orbit Sep 06 '15 at 11:59
  • @LightnessRacesinOrbit: Using a regex literal (`var reg = /^19[-\d]*/g;`) instead of the `RegExp` constructor *wouldn't* fix this. (http://jsfiddle.net/ejf08zb8/) I was just mentioning that normally, it's best to use literal syntax unless you have a good reason for using the constructor. – T.J. Crowder Sep 06 '15 at 12:02
  • Okay, I missed the "in general", I guess. Nice to know I wasn't wrong, at least :) – Lightness Races in Orbit Sep 06 '15 at 19:06