2

I found this really strange behavior when defining a regex as a class property in ES6. I currently encountered this behavior working with typescript and angular, but I made a stripped version in my chrome console and it was the same.

class RegexTest {
  constructor() {
    this.expression = /[\*|\-|\_]/g;
  }

  testStringExpression(word) {
    return this.expression.test(word);
  }
}

const tester = new RegexTest();
const words = "*, *, *, kitten";

const reduced = words.split(',').reduce((acc, item, index) => {
  if ( tester.testStringExpression(item) ) {
    return acc;
  } else {
    return acc + item + index;
  }
}, "");

console.log(reduced); // Should be " kitten3", instead I'm getting " *2 kitten3"

However, If I just test the regular expression as it is inside the reduce, the result is the expected:

const words = "*, *, *, kitten";
const reduced = words.split(',').reduce((acc, item, index) => {
  if ( /[\*|\-|\_]/g.test(item) ) {
    return acc;
  } else {
    return acc + item + index;
  }
}, "");

console.log(reduced); // " kitten3"

What am I'm getting wrong here?

Osman Cea
  • 1,467
  • 9
  • 18
  • 1
    Well just looking at your regular expression `[\*|\-|\_]` says to match either of the following characters: `*|-_` (including the vertical bar). You can change your set to `[*_-]` instead (no need to escape them all and placing `-` at the start or end of the set means you don't need to escape it). You don't need to use `or` inside a set: It doesn't work that way – ctwheels Dec 28 '17 at 15:54

1 Answers1

3

From the MDN reference:

If the regex has the global flag set, test() will advance the lastIndex of the regex. A subsequent use of test() will start the search at the substring of str specified by lastIndex (exec() will also advance the lastIndex property).

So repeatedly using .test() on the same regex object, will NOT start matching the regex from the start of the string you try to match. So the first time you try to match the string "*", it will return true, since it starts searching from the start of that string.

Since the expression is a property of the class, you reuse the same regex again, the second time starting from the end of the string you send it, instead of from the start. So by the time you try to match " *", you''l get false instead of true.

By putting the regular expression inline, you create a new regex object every time, and hence the lastIndex won't get updated.

The solution here is to use the match method of words to test the regex, instead of using the regex to test the word.

So if you replace

return this.expression.test(word);

by

return word.match( this.expression );

everything will work as you expect and you get the string " kitten3".

Edit: Or you could just rewrite the regex to not use the global flag at all.

Shilly
  • 8,511
  • 1
  • 18
  • 24
  • Ahá! I had no clue about the reusing behavior of the regex... that's good enough, thanks for your help!. – Osman Cea Dec 28 '17 at 18:22