Ozan Tunca

Senior Software Engineer πŸ–₯, Music Producer 🎹

I’m Ozan, a Senior Software Engineer working mainly with JavaScript (read TypeScript). I build scalable, maintainable, performant front-ends and back-ends. I'm also an electronic music enthusiast so don't forget to check out my SoundCloud.

String.replaceAll has landed on all major browsers. Should we refactor yet?

With the most recent version of v8, we now have several new JavaScript features available in all major browsers. One of which is String.prototype.replaceAll. It is used for replacing all occurrences of a given string or a regular expression with another string. It looks like the examples below:

'lorem ipsum dolor sit amet'.replaceAll(' ', '-');
// -> 'lorem-ipsum-dolor-sit-amet'

It is a very simple addition to String.prototype.replace that we already have. Being the micro-optimization geek that I am, I decided to take a look at how this new feature performs compared to its alternatives. The purpose of this article, therefore, is to showcase this new feature and encourage the readers to approach new features from a different perspective.

What were the alternatives so far?

Since the early days of JavaScript, the prototype of String provided us with a function called replace which in essence did the same thing as replaceAll but it replaces only the first occurrence of the searchValue. Example:

'lorem ipsum dolor sit amet'.replace(' ', '-');
// -> 'lorem-ipsum dolor sit amet'
view raw replace-single.js hosted with ❤ by GitHub

This is useful but undesirable if you wanted to replace all occurrences of a text. One solution to that problem is to use a RegExp to define the searchValue. The same example as above can easily be rewritten with a RegExp to achieve the same behaviour as replaceAll.

'lorem ipsum dolor sit amet'.replace(/ /g, '-');
// -> 'lorem-ipsum-dolor-sit-amet
view raw replace-regex.js hosted with ❤ by GitHub

This works well for our case. It is also obvious to anyone who has some basic knowledge of regular expressions.

Let's make things a bit more complicated. Say we have a case where we need our searchValue to be dynamic. For instance, assume that we get our searchValue as a parameter instead of hardcoding it. In this case, creating a regular expression from this variable gets a little more complicated. An example:

function removeFromText(text, strToBeRemoved) {
return text.replace(new RegExp(strToBeRemoved, 'g'), '');
}
view raw regexp-g.js hosted with ❤ by GitHub

This works. What the code does is also still quite clear. But generating a regular expression dynamically is not always very straight forward. Special characters in the given string can easily mess with the regular expression.

removeFromText('lorem ipsum dolor sit amet', ' ');
// -> 'loremipsumdolorsitamet' βœ… works
removeFromText('lorem+ipsum+dolor+sit+amet', '+');
// -> throws error ❌
removeFromText('lorem+ipsum+dolor+sit+amet', '+');
// -> still throws error ❌
removeFromText('lorem+ipsum+dolor+sit+amet', '\\+');
// -> 'loremipsumdolorsitamet' βœ… works

It still works. But the way it works is a lot more cryptic now. The users of the function must know that they have to sanitize the string before passing it to the function. The other alternative to this is to have removeFromText() sanitize the received input. In that case, however, we make the implementation of removeFromText() unnecessarily complicated.

The last alternative to solve the problems described above is to chain .split() and .join() to achieve to same behaviour of replaceAll.

function removeFromText(text, strToBeRemoved) {
return text.split(strToBeRemoved).join('');
}
removeFromText('lorem+ipsum+dolor+sit+amet', '+');
// -> 'loremipsumdolorsitamet' βœ…
view raw split-join.js hosted with ❤ by GitHub

Now this one covers all our use cases. The drawback of this one is that what the code does is a lot less obvious. Splitting a string into an array only to merge it again seems wasteful and it doesn't say us anything about replacing a string. It is more of a hack rather than doing what we want to do directly.

It is for all these reasons, we now have our syntactic sugar replaceAll(). I'd like to think that it should be more performant than its alternatives; considering that its closes alternative is creating and destroying an array. So I ran some benchmarks.

Let's compare the performance

I've started by generating a string that is 1300 characters long using a Lorem Ipsum generator. Then I've created test cases of several ways to replace all occurrences of a text in a string. I then used jsben.ch to benchmark these test cases. I've done this on Chrome, Safari, and Firefox. The results are below:

benchmark in Chrome Benchmark in Chrome

benchmark in Safari Benchmark in Safari

benchmark in Firefox Benchmark in Firefox

I was expecting replaceAll() to be slightly faster than the alternatives. You can imagine that I was quite surprised to see that the results dramatically change for every browser. However, replaceAll() is not the fastest in any of the browsers. It is quite disappointing to see that a feature that was added to be a helper function is actually slower than its alternatives.

You can run the same benchmarks I ran at this link.

Verdict

So the question is, should we refactor all our code?

If your code is supposed to be so performant that you are performing micro-optimizations on your code, then you should probably use the alternatives instead. A super performant code is often not needed when coding with JavaScript but it can still happen if you are developing a utility library or a framework.

If you are developing for a user-facing application, however, your code would be more readable if you use more obvious functions to achieve your goal. Therefore, it would be better to use replaceAll() and similar helpers to make the purpose of your code more obvious to the readers.

Subscribe for more

If you liked this article, please subscribe to be notified of more articles like this. I'll also be sneding some free goodies to your way. Plus I'll never spam you πŸ‘

Search content