Why is appending to the innerHTML property bad?

3 min read Original article ↗

Yes, elm.innerHTML += str; is a very bad idea. Use elm.insertAdjacentHTML('beforeend', str); as the perfect alternative.

The typical "browser has to rebuild the DOM" answer really doesn't do the question justice:

  1. First, the browser need to go through each element under elm, each of their properties, and all their text & comment & processing instruction nodes, and escape them to build you a string.

  2. Then you have a long string, which you append to. This step is OK.

  3. Third, when you set innerHTML, the browser have to remove all the elements, properties, and nodes it just went through.

  4. Then it parse the string, rebuild all the elements, properties, and nodes it just destroyed, to create a new DOM fragment that is mostly identical.

  5. Finally it attach the new nodes, and the browser have 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 scratch.

  6. But it be not done yet! The browser also have to recycle old nodes by scanning all JavaScript variables.

Problems:

  • Some state may fail to be reflected in 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 will 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 still belong to the document, but will no longer be in the DOM tree. When your code accesses them, nothing may happen or it may throw an error.

  • The round-trip of the DOM tree through string serialization is not even guaranteed to preserve the original element structure before the appending point: some DOM trees cannot be represented as markup at all, while others are not parsed identically (like when processing instruction nodes are present). As such you may end up inadvertently modifying the relative placements and contents of elements in unexpected ways.

  • The above problems mean it is unfriendly to JS plugins – the plugins may attach handlers, or keep references to old nodes and cause memory leaks.

  • 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 is spent 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 the same code, without innerHTML's problems. No DOM rebuild, no handler lost, no input reset, less memory fragmentation, no bad habits, no manual element creations and assignments.

It allows you to inject HTML string into elements in one line, including properties, and even allows you to inject composite and multiple elements. Its speed is optimised – in Mozilla's test it is one hundred and fifty 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.