What is the fastest JavaScript HTML escape implementation? To find out, I:
- Wrote 10 different JavaScript HTML escape implementations.
- Created a web-based benchmarking tool which uses web workers and the Performance API to test with a variety of string sizes and generates a downloadable CSV of results.
- Created a set of scripts to aggregate and plot the results as SVGs.
Results
The times are from 64-bit Chrome 99 running in Debian on a Lenovo Thinkpad X1 Carbon (9th Gen); the specific timing results may vary for your system, but the relative results should be comparable.
The first chart shows implementations’ mean call time (95% CI) as the string length varies:
The second chart comparse implementations’ mean call time (95% CI) for 3000 character strings:
The red, blue, and green bars in this chart indicate the slow, medium, and fast functions, respectively.
Slow Functions
Anything that uses a capturing regular expression.
Example: h2
const h2 = (() => {
// characters to match
const M = /([&<>'"])/g;
// map of char to entity
const E = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"',
};
// build and return escape function
return (v) => v.replace(M, (_, c) => E[c]);
})();
The capture is definitely at fault, because the call times for identical
non-capturing implementations (example: h4
) are comparable to
everything else.
Medium Functions
Except for the capturing regular expression implementations in the previous section, the remaining implementations’ call times were comparable with one another. This includes:
- Reducing an array of string literals and calling
replace()
. - Several variants of reducing an array of non-capturing regular
expression with
replace()
.
Example: h4
const h4 = (() => {
// characters to match
const M = /[&<>'"]/g;
// map of char to entity
const E = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"',
};
// build and return escape function
return (v) => v.replace(M, (c) => E[c]);
})();
Fast Functions
Three implementations are slightly faster than the others. They all use
replaceAll()
and match on string literals. Their call times are
indistinguishable from one another:
- h7: Reduce, Replace All
- h8: Reduce, Replace All, Frozen
- h9: Replace All Literal
Example: h7
const h7 = (() => {
const E = [
['&', '&'],
['<', '<'],
['>', '>'],
["'", '''],
['"', '"'],
];
return (v) => E.reduce((r, e) => r.replaceAll(e[0], e[1]), v);
})();
The Winner: h9
Even though the call times for h7
, h8
, and h9
are
indistinguishable, I actually prefer h9
because:
- The most legible. It is the easiest implementation to read for beginning developers and developers who are uncomfortable with functional programming.
- The simplist parse (probably).
- Slightly easier for browsers to optimize (probably).
Here it is:
// html escape (replaceall explicit)
const h9 = (v) => {
return v.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll("'", ''')
.replaceAll('"', '"');
};
Notes
- The benchmarking interface, aggregation and plotting scripts, and additional information are available in the companion GitHub repository.
- I also wrote a DOM/
textContent
implementation, but I couldn’t compare it with the other implementations because web workers don’t have DOM access. I would be surprised if it was as fast as the fast functions above. Object.freeze()
doesn’t appear to help, at least not in Chrome.
Update (2022-03-11): I posted the benchmarking tool online at the following URL: https://pmdn.org/fastest-js-html-escape/.