2

Here is a Codepen version.

My goal is to iterate over the elements of an array, search for a regex matching ~@something@~, remove those "bookends" identifying the match, then .join() the array. In the following example, the result should look like a/b/c.

However instead, I get the result a/~@b@~/c. To make things even more confusing, when I reverse the order of the array elements, the problem result changes from the ~@b@~ to the ~@c@~. And, finally, to make things downright weird, the problem seems to resolve itself by adding a simple test method which, itself, always returns a value of false. See comments for those in code. And see for yourself.

What is causing this strange behavior? And what is the correct way to iterate over these elements and do the substitution I describe?

function myFunction() {
  var a = ["a", "~@b@~", "~@c@~"]
  var re = /~@(.*?)@~/g;
  var i = a.length;
  //  Uncomment below line to see the problem change from ~@b@~ to ~@c@~
  //a.reverse();
  while (i--) {
    console.log('i', i, 'str', a[i]);
    var match = re.exec(a[i]);
    console.log('match', match);
    // Uncomment the below line to see it work properly.
    //console.log('test', re.test(a[i]));
    if (match && match[0] && match[1]) {
      a[i] = a[i].replace(match[0], match[1]);
      console.log('a[i]', a[i]);
    }
  }
  var res = a.join('/');
  document.getElementById("demo").innerHTML = res;
}
<p>
  My goal is to print the string: <code>a/b/c</code>. See weird <i>uncomment</i> fix in JS.
  <button onclick="myFunction()">Click Here</button>
</p>
<p id="demo"></p>
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207

2 Answers2

2

The reason for the behavior is that your regex has the global flag (g) but you're only executing it once; that means its lastIndex is set and the next time you run it, it tries to start from where it left off.

Either remove the g flag, or add

re.lastIndex = -1;

...as the first line in the while loop:

function myFunction() {
  var a = ["a", "~@b@~", "~@c@~"]
  var re = /~@(.*?)@~/;             // <=== Note `g` removed
  var i = a.length;
  var t;
  while (i--) {
    var match = re.exec(a[i]);
    if (match && match[0] && match[1]) {
      a[i] = a[i].replace(match[0], match[1]);
    }
  }
  var res = a.join('/');
  document.getElementById("demo").innerHTML = res;
}
<p>
  My goal is to print the string: <code>a/b/c</code>. See weird <i>uncomment</i> fix in JS.
  <button onclick="myFunction()">Click Here</button>
</p>
<p id="demo"></p>

However, if you leave that g flag on the regex, you can replace the entire contents of the while loop with

a[i] = a[i].replace(re, "$1");

function myFunction() {
  var a = ["a", "~@b@~", "~@c@~"]
  var re = /~@(.*?)@~/;
  var i = a.length;
  var t;
  //  Uncomment below line to see the problem change from ~@b@~ to ~@c@~
  //a.reverse();
  while (i--) {
    a[i] = a[i].replace(re, "$1");
  }
  var res = a.join('/');
  document.getElementById("demo").innerHTML = res;
}
<p>
  My goal is to print the string: <code>a/b/c</code>. See weird <i>uncomment</i> fix in JS.
  <button onclick="myFunction()">Click Here</button>
</p>
<p id="demo"></p>

...which also has the advantage of handling entries in the form "~@b@~~@c@~" (because it replaces all occurrences in the string, not just the first one).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • +1. But suppose the content of the `"$1"` variable is only a variable *name* which I want to replace with its value? `a[i] = a[i].replace(re, eval("$1"));` doesn't work. Any suggestions? I can put this question in a different SO question if that would help? – Let Me Tink About It Nov 27 '16 at 17:59
  • 1
    @Mowzer: In that case, you'll need your loop, just without the `g` flag or with `re.lastIndex = -1`. – T.J. Crowder Nov 27 '16 at 18:04
1

Removing the global flag from the regex to change from:

var re = /~@(.*?)@~/g;

to:

var re = /~@(.*?)@~/;

should fix this.

Bardy
  • 2,100
  • 1
  • 13
  • 11