Why is "element.innerHTML+=" bad code?

Viewed 53358

I have been told not to append stuff using element.innerHTML += ... like this:

var str = "<div>hello world</div>";
var elm = document.getElementById("targetID");

elm.innerHTML += str; //not a good idea?

What is wrong with it?, what other alternatives do I have?

9 Answers

Short

If you change innerHTML += ... (update content) to innerHTML = ... (regenerate content) then you will get very fast code. It looks like the slowest part of += is READING DOM content as string (not transforming string to DOM)

Drawback of using innerHTML is that you loose old content event handlers - however you can use tag arguments to omit this e.g. <div onclick="yourfunc(event)"> which is acceptable in small projects

Long

I made performance tests HERE on Chrome, Firefox and Safari (2019 May) (you can run them in your machine but be patient - it takes ~5 min)

enter image description here

function up() {
  var container = document.createElement('div');
  container.id = 'container';
  container.innerHTML = "<p>Init <span>!!!</span></p>"
  document.body.appendChild(container);
}

function down() {
  container.remove()
}

up();

// innerHTML+=
container.innerHTML += "<p>Just first <span>text</span> here</p>";
container.innerHTML += "<p>Just second <span>text</span> here</p>";
container.innerHTML += "<p>Just third <span>text</span> here</p>";

down();up();

// innerHTML += str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML += s;

down();up();

// innerHTML = innerHTML+str
var s=container.innerHTML+'';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// innerHTML = str
var s="<p>Init <span>!!!</span></p>";
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// insertAdjacentHTML str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.insertAdjacentHTML("beforeend",s);

down();up();

// appendChild
var p1 = document.createElement("p");
var s1 = document.createElement("span"); 
s1.appendChild( document.createTextNode("text ") );
p1.appendChild( document.createTextNode("Just first ") );
p1.appendChild( s1 );
p1.appendChild( document.createTextNode(" here") );
container.appendChild(p1);

var p2 = document.createElement("p");
var s2 = document.createElement("span"); 
s2.appendChild( document.createTextNode("text ") );
p2.appendChild( document.createTextNode("Just second ") );
p2.appendChild( s2 );
p2.appendChild( document.createTextNode(" here") );
container.appendChild(p2);

var p3 = document.createElement("p");
var s3 = document.createElement("span"); 
s3.appendChild( document.createTextNode("text ") );
p3.appendChild( document.createTextNode("Just third ") );
p3.appendChild( s3 );
p3.appendChild( document.createTextNode(" here") );
container.appendChild(p3);

down();up();

// insertAdjacentHTML
container.insertAdjacentHTML("beforeend","<p>Just first <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just second <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just third <span>text</span> here</p>");

down();up();

// appendChild and innerHTML
var p1 = document.createElement('p');
p1.innerHTML = 'Just first <span>text</span> here';
var p2 = document.createElement('p');
p2.innerHTML = 'Just second <span>text</span> here';
var p3 = document.createElement('p');
p3.innerHTML = 'Just third <span>text</span> here';
container.appendChild(p1);
container.appendChild(p2);
container.appendChild(p3);
b {color: red}
<b>This snippet NOT test anythig - only presents code used in tests</b>


  • For all browsers the innerHTML += was the slowest solutions.
  • The fastest solution for chrome appendChild - it is ~38% faster than second fast solutions but it is very unhandy. Surprisingly on Firefox appendChild was slower than innerHTML =.
  • The second fast solutions and similar performance we get for insertAdjacentHTML str and innerHTML = str
  • If we look closer on case innerHTML = innerHTML +str and compare with innerHTML = str it looks like the slowest part of innerHTML += is READING DOM content as string (not transforming string to DOM)
  • If you want change DOM tree generate first whole string(with html) and update/regenerate DOM only ONCE
  • mixing appendChild with innerHTML= is actually slower than pure innerHTML=

Another reason why "element.innerHTML+=" is bad code is because changing innerHTML property directly has been recently found to be insecure in principle, which is now reflected e.g. in Mozilla's warning about it.

Here is a Javascript security/XSS validation-safe/proof practical example of working without using innerHTML property or insertAdjacentHTML method. The content of some htmlElement gets updated-replaced with a new HTML content:

const parser = new DOMParser(),
      tags = parser.parseFromString('[some HTML code]'), `text/html`).body.children, 
      range = document.createRange();
range.selectNodeContents(htmlElement);
range.deleteContents();
for (let i = tags.length; i > 0; i--)
{
     htmlElement.appendChild(tags[0]); 
     // latter elements in HTMLCollection get automatically emptied out with each use of
     // appendChild method, moving later elements to the first position (0), so 'tags' 
     // can not be walked through normally via usual 'for of' loop
}

However, the parser-generated DOM might be too safe for cases when you need to insert script nodes which might end up appearing in DOM but not executed. In such cases, one might want to use this method:

const fragment = document.createRange().createContextualFragment('[some HTML code]');

One way to do it (but not performance tested) : (inspired by DDRRSS response)

    const parser = new DOMParser();
    const parsedBody = parser.parseFromString(str, 'text/html').body;
    for(let i = 0; i <= parsedBody.childNodes.length; i++){
        const tag = parsedBody.childNodes[i];
        if(!tag) continue;

        if(tag instanceof Text){
            codeElement.append(document.createTextNode(tag.textContent));
        } else if(tag instanceof HTMLElement){
            codeElement.appendChild(tag.cloneNode(true));
        }}

    codeElement.appendChild(document.createTextNode(parsedBody.innerText));
Related