html encode

JavaScript performance comparison

Revision 9 of this test case created by Eamon Nerbonne

Info

Speed test comparing different methods of escaping html.

A loop with an if-statement using char-codes is the fastest (or almost so) method in all browsers. (entry "loop + if-charcode; append")

Preparation code

<pre id="output_vis"></pre>
<script>
var docTxts = [];
window.onload = function() {
   var docTxt = document.documentElement.innerHTML.substr(0,20000);
   do{
      docTxts.push(docTxt);
      docTxt = docTxt.substr(0,Math.floor(docTxt.length*0.6));
   } while(docTxt.length>1);
};
</script>
<script>
Benchmark.prototype.setup = function() {
    function encodeWithIfs(str) {
      var result = ""
      for (var cc = 0; cc < str.length; cc++) {
        var c = str.charAt(cc)
        if (c === "&") result += '&amp;'
        else if (c === '"') result += '&quot;'
        else if (c === "'") result += '&#39;'
        else if (c === "<") result += '&lt;'
        else if (c === ">") result += '&gt;'
        else result += c
      }
      return result
    }
   
    function endcodeWithRegEx(str) {
      return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    }
   
    function endcodeWithRegEx2(str) {
      return str.replace(/[&<>"'`]/g, function (chr) {
        return '&#' + chr.charCodeAt(0) + ';'
      })
    }
   
    function encodeWithSwitch(str) {
       var c, result = "", length = str.length;
      for (var i = 0; i < length; i++) {
        switch(c = str.charAt(i)) {
          case "&": c = "&amp;"; break;
          case '"': c = "&quot;"; break;
          case "'": c = "&#39;"; break;
          case "<": c = "&lt;"; break;
          case ">": c = "&gt;"; break;
        }
        result += c;
      }
      return result;
    }
   
    var encodeWithObjectProps = function(){
      var map = {'"':'&#34;','&':'&amp;',"'":'&#39;','<':'&lt;','>':'&gt;'};
      return function (str) {
        var result = "", len = str.length, doneUpto = 0;
        for (var i=0; i < len ; i++) {
          var char = str.charAt(i);
          var ref = map[char];
          if(ref === undefined) continue;
          result += doneUpto === i ? ref : str.substring(doneUpto, i) + ref;
          doneUpto = i + 1;
        }
        if(doneUpto !== len) result += str.substring(doneUpto, len);
        return result;
      }
    }();
   
    function encodeByCharCodeJoin(str) {
      var res = [], len = str.length, doneUpto = 0;
      for (var i=0; i < len; i++) {
        var c, code = str.charCodeAt(i);
        if(code > 62) continue;
        else if(code === 38) c = "&amp;";
        else if(code === 34) c = "&quot;";
        else if(code === 39) c = "&apos;";
        else if(code === 60) c = "&lt;";
        else if(code === 62) c = "&gt;";
        else continue;
        if(doneUpto !== i) res.push(str.substring(doneUpto, i));
        res.push(c);
        doneUpto = i+1;
      }
      if(doneUpto !== len) res.push(str.substring(doneUpto, len));
      return res.join("");
    }
   
    function encodeByCharCodeAppend(str) {
      var result = "", len = str.length, doneUpto = 0;
      for (var i=0; i < len ; i++) {
        var ref, code = str.charCodeAt(i);
        if(code > 62) continue; //fast-path
        else if(code === 38) ref = "&amp;";
        else if(code === 34) ref = "&#34;";
        else if(code === 39) ref = "&#39;";
        else if(code === 60) ref = "&lt;";
        else if(code === 62) ref = "&gt;";
        else continue;
        result += doneUpto === i  ?  ref :  str.substring(doneUpto, i) + ref;
        doneUpto = i + 1;
      }
      if(doneUpto !== len) result += str.substring(doneUpto, len);
      return result;
    }
   
    var encodeByCharCodeLookup = (function(){
      var map = {'"':'&#34;','&':'&amp;',"'":'&#39;','<':'&lt;','>':'&gt;'};
      var lookup = [];
      for(var j=0;j<63;j++) lookup[j] = map[String.fromCharCode(j)];
      return function (str) {
        var result = "", len = str.length, doneUpto = 0;
        for (var i=0; i < len ; i++) {
          var code=str.charCodeAt(i);
          if(code>62) continue;
          var ref = lookup[code];
          if(ref === undefined) continue;
          result += doneUpto === i ? ref : str.substring(doneUpto, i) + ref;
          doneUpto = i + 1;
        }
        if(doneUpto !== len) result += str.substring(doneUpto, len);
        return result;
      };
    })();
   
   
    var encodeViaDom = (function(){ //limited: doesn't encode ' and "
        var el = document.createElement('div');
        return function(val) {
            el.textContent = val;
            return el.innerHTML;
        };
    })();
   
    var encodeWithRegExpAndObject = (function() {
      var o={"&":"&amp;", '"':"&quot;", "'":"&#39;", "<":"&lt;", ">":"&gt;"};
      function mapchar(chr) { return o[chr]; }
      return function (str) { return str.replace(/[&"'<>]/g, mapchar); };
    })();
   
    //buggy, doesn't work in IE10, not in chrome 23, and not in FF20
    function encodeWithStringPrototype(str) {
      return ''.link(str).slice(9,-6).replace(/&/g, '&amp;');
    }
   
   
    function doEncode(func) {
      //ensure JIT doesn't omit call entirely by storing output in an observable location.  Also, use this to debug...
      window.output_sink = [
        func("All replacement chars & \" ' < > "+docTxts[0].length)
      ];
      for(var i =0;i<docTxts.length;i++) {
         var iters = Math.sqrt(docTxts[0].length / docTxts[i].length);
         for(var j=0;j<iters;j++)
            window.output_sink.push(func(docTxts[i]));
      }
    }
};

Benchmark.prototype.teardown = function() {
    document.getElementById('output_vis').textContent = window.output_sink[0];
};
</script>

Preparation code output


Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
loop + if-charAt
doEncode(encodeWithIfs);
pending…
multiple regexes
doEncode(endcodeWithRegEx);
pending…
regex + match func
doEncode(endcodeWithRegEx2);
pending…
loop + object lookup
doEncode(encodeWithObjectProps);
pending…
regex + object lookup
doEncode(encodeWithRegExpAndObject);
pending…
using DOM innerHtml
doEncode(encodeViaDom);
pending…
loop + if-charcode; join
doEncode(encodeByCharCodeJoin);
 
pending…
loop + if-charcode; append
doEncode(encodeByCharCodeAppend);
pending…
loop + charcode-lookup; append
doEncode(encodeByCharCodeLookup);
pending…
loop + switch-charAt
doEncode(encodeWithSwitch);
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment