12

Possible Duplicate:
Interesting test of Javascript RegExp
Regular expression test can't decide between true and false (JavaScript)

Example of issue. When ran inline the results are as I would expect. But when stored as a variable it skips the middle span element.

// Inline RegExp
function getToggleClasses() {
  var toggler = [],
      elements = document.getElementsByTagName("*"),
      i=0,
      len = elements.length;

  for (i; i < len; i++) {
    if (/toggler/g.test(elements[i].className)) {
      toggler.push(elements[i]);
    }
  }

  document.getElementById('results').innerHTML += "<br />Inline: " + toggler.length;
}

// Variable
function getToggleClasses2() {
  var toggler = [],
      elements = document.getElementsByTagName("*"),
      tester = /toggler/g,
      i=0,
      len = elements.length;

  for (i; i < len; i++) {
    if (tester.test(elements[i].className)) {
      toggler.push(elements[i]);
    }
  }

  document.getElementById('results').innerHTML += "<br />Variable: " + toggler.length;
}
​

Mark up:

<span class="toggler">A</span>
<span class="toggler">B</span>
<span class="toggler">C</span>

Given: I understand there is no reason to use a RegExp to do this comparison and I also understand how great libraries such as jQuery are. I also know that the g is not needed in this case.

I can't understand why these two methods should ever return different results.

Community
  • 1
  • 1
Joe
  • 80,724
  • 18
  • 127
  • 145
  • This is just personal preference, but I think it would improve clarity a bit to put parenthesis around a regex literal that has flags when calling a function on the literal. – JAB Jun 13 '12 at 19:40
  • 1
    @apsillers, you are correct. What did you search to find those? I couldn't find out what to search to get the results I was after. – Joe Jun 13 '12 at 20:04
  • I cheated a little and searched for `[javascript] regex test lastindex` -- I was quite certain that a question like this had been asked before, and I knew the answer would include the text `lastIndex`. This is a good case showing that the existence of a duplicate question isn't necessarily an indication that the asker has been negligent; you've asked a well-phrased question for a difficult-to-search problem. – apsillers Jun 13 '12 at 20:44

3 Answers3

9

RegExp instances are stateful, so reusing them can cause unexpected behavior. In this particular case, it's because the instance is global, meaning:

that the regular expression should be tested against all possible matches in a string.

That's not the only difference caused by using g, however. From RegExp.test @ 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.


Remove the g flag, or set lastIndex to 0 (thanks, @zzzzBov).

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
3

/g is not needed and should not be used in this case.

The behavior differs in these cases because in the "inline" case the regex object is recreated each iteration of the loop. While in the variable is created once, and keeps its state (lastIndex) between loop iterations.

Move the var into the loop and you will get the same result:

// Variable
function getToggleClasses2() {
  var toggler = [],
      elements = document.getElementsByTagName("*"),
      i=0,
      len = elements.length;

  for (i; i < len; i++) {
    var tester = /toggler/g;
    if (tester.test(elements[i].className)) {
      toggler.push(elements[i]);
    }
  }

  document.getElementById('results').innerHTML += "<br />Variable: " + toggler.length;
}
Qtax
  • 33,241
  • 9
  • 83
  • 121
  • 1
    Yeah, I got that. But a why is what I'm very curious to know. Because `/toggler/g.test('toggler'); // true` – Joe Jun 13 '12 at 19:34
  • 1
    @Joe, you wrote in the question *"I also know that the `g` is needed"*, that's why I state that it's not. – Qtax Jun 13 '12 at 19:46
1

The regex maintains a variable called lastIndex, which is the index to start the next search. From 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.

When you define an inline regex for each iteration, the state is lost and lastIndex is always 0 because you have a fresh regex each time. If you keep the regex in a veriable, the lastIndex is saved as the ending position of the last match, which in this case causes the next search for begin at the end of the next string, resulting in a failed match. When the third comparison comes around, the lastIndex has been reset to 0 because the regex knows it got no results last time.

apsillers
  • 112,806
  • 17
  • 235
  • 239