In practice, what are the advantages of using createElement over innerHTML? I am asking because I'm convinced that using innerHTML is more efficient in terms of performance and code readability/maintainability but my teammates have settled on using createElement as the coding approach. I just wanna understand how createElement can be more efficient.
User bobince puts a number of cons very, very well in his critique of jQuery.
... Plus, you can make a div by saying $(''+message+'') instead of having to muck around with document.createElement('div') and text nodes. Hooray! Only... hang on. You've not escaped that HTML, and have probably just created a cross-site-scripting security hole, only on the client side this time. And after you'd spent so long cleaning up your PHP to use htmlspecialchars on the server-side, too. What a shame. Ah well, no-one really cares about correctness or security, do they?
jQuery's not wholly to blame for this. After all, the innerHTML property has been about for years, and already proved more popular than DOM. But the library certainly does encourage that style of coding.
As for performance: InnerHTML is most definitely going to be slower, because it needs to be parsed and internally converted into DOM elements (maybe using the createElement
method).
InnerHTML is faster in all browsers according to the quirksmode benchmark provided by @Pointy.
As for readability and ease of use, you will find me choosing innerHTML
over createElement
any day of the week in most projects. But as you can see, there are many points speaking for createElement
.
There are several advantages to using createElement
instead of modifying innerHTML
(as opposed to just throwing away what's already their and replacing it) besides safety, like Pekka already mentioned:
Preserves existing references to DOM elements when appending elements
When you append to (or otherwise modify) innerHTML
, all the DOM nodes inside that element have to be re-parsed and recreated. If you saved any references to nodes, they will be essentially useless, because they aren't the ones that show up anymore.
Preserves event handlers attached to any DOM elements
This is really just a special case (although common) of the last one. Setting innerHTML
will not automatically reattach event handlers to the new elements it creates, so you would have to keep track of them yourself and add them manually. Event delegation can eliminate this problem in some cases.
Could be simpler/faster in some cases
If you are doing lots of additions, you definitely don't want to keep resetting innerHTML
because, although faster for simple changes, repeatedly re-parsing and creating elements would be slower. The way to get around that is to build up the HTML in a string and set innerHTML
once when you are done. Depending on the situation, the string manipulation could be slower than just creating elements and appending them.
Additionally, the string manipulation code may be more complicated (especially if you want it to be safe).
Here's a function I use sometimes that make it more convenient to use createElement
.
function isArray(a) {
return Object.prototype.toString.call(a) === "[object Array]";
}
function make(desc) {
if (!isArray(desc)) {
return make.call(this, Array.prototype.slice.call(arguments));
}
var name = desc[0];
var attributes = desc[1];
var el = document.createElement(name);
var start = 1;
if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
for (var attr in attributes) {
el[attr] = attributes[attr];
}
start = 2;
}
for (var i = start; i < desc.length; i++) {
if (isArray(desc[i])) {
el.appendChild(make(desc[i]));
}
else {
el.appendChild(document.createTextNode(desc[i]));
}
}
return el;
}
If you call it like this:
make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);
you get the equivalent of this HTML:
<p>Here is a <a href="http://www.google.com/">link</a>.</p>
While innerHTML
may be faster, I don't agree that it is better in terms of readability or maintenance. It may be shorter to put everything in one string, but shorter code is not always necessarily more maintainable.
String concatenation just does not scale when dynamic DOM elements need to be created as the plus' and quote openings and closings becomes difficult to track. Consider these examples:
The resulting element is a div with two inner spans whose content is dynamic. One of the class names (warrior) inside the first span is also dynamic.
<div>
<span class="person warrior">John Doe</span>
<span class="time">30th May, 2010</span>
</div>
Assume the following variables are already defined:
var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';
Using just innerHTML and mashing everything into a single string, we get:
someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";
The above mess can be cleaned up with using string replacements to avoid opening and closing strings every time. Even for simple text replacements, I prefer using replace
instead of string concatenation.
This is a simple function that takes an object of keys and replacement values and replaces them in the string. It assumes the keys are prefixed with $
to denote they are a special value. It does not do any escaping or handle edge cases where $
appears in the replacement value etc.
function replaceAll(string, map) {
for(key in map) {
string = string.replace("$" + key, map[key]);
}
return string;
}
var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
type: personClass,
name: personName,
date: date
});
someElement.innerHTML = html;
This can be improved by separating the attributes, text, etc. while constructing the object to get more programmatic control over the element construction. For example, with MooTools we can pass object properties as a map. This is certainly more maintainable, and I would argue more readable as well. jQuery 1.4 uses a similar syntax to pass a map for initializing DOM objects.
var div = new Element('div');
var person = new Element('span', {
'class': 'person ' + personClass,
'text': personName
});
var when = new Element('span', {
'class': 'time',
'text': date
});
div.adopt([person, when]);
I wouldn't call the pure DOM approach below to be any more readable than the ones above, but it's certainly more maintainable because we don't have to keep track of opening/closing quotes and numerous plus signs.
var div = document.createElement('div');
var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));
var when = document.createElement('span');
when.className = 'date';
when.appendChild(document.createTextNode(date));
div.appendChild(person);
div.appendChild(when);
The most readable version would most likely result from using some sort of JavaScript templating.
<div id="personTemplate">
<span class="person <%= type %>"><%= name %></span>
<span class="time"><%= date %></span>
</div>
var div = $("#personTemplate").create({
name: personName,
type: personClass,
date: date
});