Why is "element.innerHTML+=" bad code?
The alternative is .createElement()
, .textContent
, and .appendChild()
. Appending with +=
is only an issue if you're dealing with a lot of data.
Demo: http://jsfiddle.net/ThinkingStiff/v6WgG/
Script
var elm = document.getElementById( 'targetID' ),
div = document.createElement( 'div' );
div.textContent = 'goodbye world';
elm.appendChild( div );
HTML
<div id="targetID">hello world</div>
Every time innerHTML
is set, the HTML has to be parsed, a DOM constructed, and inserted into the document. This takes time.
For example, if elm.innerHTML
has thousands of divs, tables, lists, images, etc, then calling .innerHTML += ...
is going to cause the parser to re-parse all that stuff over again. This could also break references to already constructed DOM elements and cause other chaos. In reality, all you want to do is append a single new element to the end.
It's better to just call appendChild
:
var newElement = document.createElement('div');
newElement.innerHTML = '<div>Hello World!</div>';
elm.appendChild(newElement);
This way, the existing contents of elm
are not parsed again.
NOTE: It's possible that [some] browsers are smart enough to optimize the +=
operator and not re-parse the existing contents. I have not researched this.
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)
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 FirefoxappendChild
was slower thaninnerHTML =
. - The second fast solutions and similar performance we get for
insertAdjacentHTML str
andinnerHTML = str
- If we look closer on case
innerHTML = innerHTML +str
and compare withinnerHTML = str
it looks like the slowest part ofinnerHTML +=
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
withinnerHTML=
is actually slower than pureinnerHTML=
Yes, elm.innerHTML += str;
is a very bad idea.
Use elm.insertAdjacentHTML( 'beforeend', str )
as the perfect alternative.
The typical "browser has to rebuild DOM" answer really doesn't do the question justice:
First the browser need to go through each elements under elm, each of their properties, and all their texts & comments & process nodes, and escape them to build you a string.
Then you have a long string, which you append to. This step is ok.
Third, when you set innerHTML, browser has to remove all the elements, properties, and nodes it just went through.
Then it parse the string, build from all the elements, properties, and nodes it just destroyed, to create a new DOM fragment that is mostly identical.
Finally it attach the new nodes, and the browser has to layout the whole thing. This may be avoidable (see the alternative below), but even if the appended node(s) requires a layout, old nodes would have their layout properties cached instead of re-calculated from fresh.
But it's not done yet! The browser also have to recycle old nodes by scanning all javascript variables.
Problems:
Some properties may not be reflected by HTML, for example the current value of
<input>
will be lost and reset to the initial value in the HTML.If you have any event handlers on the old nodes, they will be destroyed and you have to reattach all of them.
If your js code is referencing any old nodes, they will not be destroyed but will instead be orphaned. They belong to the document but is no longer in the DOM tree. When your code access them, nothing may happen or it may throw error.
Both problems mean it is unfriendly with js plugins - the plugins may attach handlers, or keep reference to old nodes and cause memory leak.
If you get into the habit of doing DOM manipulation with innerHTML, you may accidentally change properties or do other things you didn't want to.
The more nodes you have, the more inefficient this is, the more battery juice for nothing.
In short, it is inefficient, it is error prone, it is simply lazy and uninformed.
The best alternative is Element.insertAdjacentHTML
, that I haven't seen other answers mention:
elm.insertAdjacentHTML( 'beforeend', str )
Almost same code, without innerHTML's problems. No rebuild, no handler lost, no input reset, less memory fragmentation, no bad habit, no manual element creations and assignments.
It allows you to inject html string into elements in one line, including properties, and even allows yow to inject composite and multiple elements. Its speed is optimised - in Mozilla's test it is 150 times faster.
In case someone tell you it is not cross browser, it is so useful that it is HTML5 standard and available on all browsers.
Don't ever write
elm.innerHTML+=
again.