Fastest JavaScript HTML Escape

March 9, 2022

What is the fastest JavaScript HTML escape implementation? To find out, I:

  1. Wrote 10 different JavaScript HTML escape implementations.
  2. 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.
  3. 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:

String Size vs. HTML Escape Function Call Time (μs)

String Size vs. HTML Escape Function Call Time (μs)

The second chart comparse implementations' mean call time (95% CI) for 3000 character strings:

HTML Escape Function Call Times

HTML Escape Function Call Times

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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&apos;',
    '"': '&quot;',
  };

  // 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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&apos;',
    '"': '&quot;',
  };

  // 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 = [
    ['&', '&amp;'],
    ['<', '&lt;'],
    ['>', '&gt;'],
    ["'", '&apos;'],
    ['"', '&quot;'],
  ];

  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('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll("'", '&apos;')
    .replaceAll('"', '&quot;');
};

 

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/.