loading...

TIL: JavaScript replace() command with callback

huytd profile image Huy Tr. ・1 min read

Of course, this is not new, it's already here centuries ago in the document https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace, but I never have to do any replacement complicated enough to use it, so I pay no attention to it until I read a pull request from a teammate today.

The replace() command in JavaScript has a callback that provided you with some more information such as the matched content, the index, and the original string. What you return in that callback will be replaced to the matched content.

This allows you to have a more complicated replacement, for example: you want to replace just the second occurrence of the letter "a" in "abcabc"to the letter "$".

How would you write a regex for that? What if we change the requirement
to any nth occurrence? Even if you find a regex solution, is it
elegant enough to not making any other developer vomit when they had to maintain your code?

Using replace() with a callback, we can just write:

"abcabc".replace(/a/g, (matched, index, original) => {
  if (index !== 0) {
    return "$";
  } else {
    return matched;
  }
});

That's it, stop writing complicated regexes, start using replace()
with callbacks, it makes things easier. You and your teammates, all have a life to live, and sanity to save.

Posted on Oct 2 '17 by:

huytd profile

Huy Tr.

@huytd

Write code, drawing things

Discussion

markdown guide
 

Yes, replace with a callback is pretty powerful. However, keep in mind that regular expressions are meant to solve regular problems and will easily fail on irregular tasks.

Also, your understanding of the syntax is slightly imcomplete, it actually is:

String.prototype.replace(regExp, callback(
  fullMatch: string,
  ...capturedMatches: string[],
  positionOfFullMatchInString: number,
  inputString: string
))

Captured matches are what you get from using parentheses in your RegExp. The position in the string like all string functions in JS counts UTF-16/-32 chars as multiple characters. Unlike a lot of other methods, the callback is not run in the scope of the object it is called on, so this will default to the global scope unless manually bound.

 

Your code is not completely correct. The index variable in the callback is actually the position of the match in the string. So if the task is still matching the 2nd occurrence in the string, your code will fail for this string: "abcabca" and the index for each match will be 0, 3, 8 and not 0, 1, 2. However, we can just add a variable to count the index of each occurrence so overall, nice discovery.

Fun fact: That index variable is also accessible in the regex itself using the lastIndex property.

 

Yeah, that's what I noticed after posted it here, to actually replace the nth match, we can write something like this:

const replaceNth = (input, search, replacement, nth) => {
    let occurrence = 0;
    return input.replace(search, matched => {
        occurrence++;
        if (occurrence === nth) return replacement;
        return matched;
    });
};
 

Technically a "callback function" is one that's called at a later point in time, like asynchronously, as in "we'll call you back later".

In this case it's just a "function", nothing so exotic. This function is called immediately if/when a substitution is to be performed.

It's easy to understate how powerful a tool this can be though, so good article about using it!

 

Bro, you don't even need JS if you know how to write good regexs. #joke