I remember well that using the DOM implementation to create new HTML elements on a document was considered to be very much slower than assigning an HTML string to the 'innerHTML' property of the applicable HTML element.
Does the same apply when authoring XML documents using JavaScript? Rather than using the DOM implementation's various create methods, would it be faster to just generate the XML string and parsing it?
Just something I wondered about.... :)
*EDIT - Added an example *
Which is faster? (I'll be using jQuery's parseXML function to do the parsing example):
var myXdoc = $.parseXML("<person><name>Bob</name><relation>Uncle</relation>");
Or
var myXdoc
if (window.ActiveXObject) {
myXdoc = new ActiveXObject("Microsoft.XMLDOM");
myXdoc.async = false;
}
else if (document.implementation && document.implementation.createDocument)
myXdoc = document.implementation.createDocument("", "", null);
var p = myXdoc.documentElement.appendChild(myXdoc.createElement("person"));
var n = p.appendChild(myXdoc.createElement("name"));
n.appendChild(myXdoc.createTextNode("Bob"));
var r = p.appendChild(myXdoc.createElement("relation"));
r.appendChild(myXdoc.createTextNode("Uncle"));
The first thing we have to know why createDocument() might be slow. The reason is that the DOM is alive and if you are modifying it, it triggers a re-validation of the DOM tree and probably a redraw of the site. Every time. But we could avoid this unnecessary re-validation and re-draw by using createDocumentFragment(). The DocumentFragment isn't part of the DOM and so it wont trigger any events. So you can build your complete DOM part and in the last step append it to the DOM tree. I think it's the fastest way to create large DOM parts.
UPDATE
I tested it in Firefox 7 using Firebug. The code:
console.time("a");
for(var i=0; i<1000; i++) {
$.parseXML("<person><name>Bob</name><relation>Uncle</relation></person>")
}
console.timeEnd("a");
console.time("b");
for(var i=0; i<1000; i++) {
var myXdoc
if (document.createDocumentFragment) {
myXdoc = document.createDocumentFragment();
}
var p = myXdoc.appendChild(document.createElement("person"));
var n = p.appendChild(document.createElement("name"));
n.appendChild(document.createTextNode("Bob"));
var r = p.appendChild(document.createElement("relation"));
r.appendChild(document.createTextNode("Uncle"));
}
console.timeEnd("b");
The result: "a" about 140ms and "b" about 35ms. So the string parse version is slower.
UPDATE2
It's very likely that the second variant is faster in any other browser, too. Because the parse method has to build the DOM object too and it's very likely that it uses the same methods (e.g.: document.createElement). So the parse method can't be faster. But it's slower because it has first to parse the string.
Related
I have some data in a sql table. I send it via JSON to my JavaScript.
From there I need to compose it into HTML for display to the user by 1 of 2 ways.
By composing the html string and inserting into .innerHTML property of the holding element
By using createElment() for each element I need and appending into the DOM directly
Neither of the questions below gives a quantifiable answer.
From first answer in first link, 3rd Reason ( first two reasons stated don't apply to my environment )
Could be faster in some cases
Can someone establish a base case of when createElement() method is faster and why?
That way people could make an educated guess of which to use, given their environment.
In my case I don't have concerns for preserving existing DOM structure or Event Listeners. Just efficiency ( speed ).
I am not using a library regarding the second link I provided. But it is there for those who may.
Research / Links
Advantages of createElement over innerHTML?
JavaScript: Is it better to use innerHTML or (lots of) createElement calls to add a complex div structure?
Adding to the DOM n times takes n times more time than adding to the DOM a single time. (:P)
This is the logic I'm personally following.
In consequence, when it is about to create, for instance, a SELECT element, and add to it several options, I prefer to add up all options at once using innerHTML than using a createElement call n times.
This is a bit the same as to compare BATCH operation to "one to one"... whenever you can factorise, you should!
EDIT: Reading the comments I understand that there's a feature (DOM DocumentFragment) that allow us saving such overhead and at the same time taking advantage of the DOM encapsulation. In this case, if the performances are really comparable, I would definitely not doubt and chose the DOM option.
I thought I read somewhere that the createElement and appendElement is faster. It makes sense, considering document.write() and innerHTML have to parse a string, and create and append the elements too. I wrote a quick test to confirm this:
<html>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
function inner() {
var test = '';
for (var i=0; i<10000; i++) {
test += '<p>bogus link with some other <strong>stuff</strong></p>';
}
console.time('innerHTML');
document.getElementById('test').innerHTML = test;
console.timeEnd('innerHTML');
}
function jq() {
var test = '';
for (var i=0; i<10000; i++) {
test += '<p>bogus link with some other <strong>stuff</strong></p>';
}
console.time('jquery');
jQuery('#test').html(test);
console.timeEnd('jquery');
}
function createEl() {
var dest = document.getElementById('test');
console.time('createel');
//dest.innerHTML = '';//Not IE though?
var repl = document.createElement('div');
repl.setAttribute('id','test');
for (var i=0; i<10000; i++) {
var p = document.createElement('p');
var a = document.createElement('a');
a.setAttribute('href','../'); a.setAttribute('target','_blank');
a.appendChild(document.createTextNode("bogus link"));
p.appendChild(a);
p.appendChild(document.createTextNode(" with some other "));
var bold = document.createElement('strong');
bold.appendChild(document.createTextNode("stuff"));
p.appendChild(bold);
repl.appendChild(p);
}
dest.parentNode.replaceChild(repl,dest);
console.log('create-element:');
console.timeEnd('createel');
}
</script>
<button onclick="inner()">innerhtml</button>
<button onclick="jq()">jquery html</button>
<button onclick="createEl()">Create-elements</button>
<div id="test">To replace</div>
</body>
</html>
In this example, the createElement - appendChild method of writing out HTML works significantly faster than innerHTML/jQuery!
I'm writing an extension to jQuery that adds data to DOM elements using
el.data('lalala', my_data);
and then uses that data to upload elements dynamically.
Each time I get new data from the server I need to update all elements having
el.data('lalala') != null;
To get all needed elements I use an extension by James Padolsey:
$(':data(lalala)').each(...);
Everything was great until I came to the situation where I need to run that code 50 times - it is very slow! It takes about 8 seconds to execute on my page with 3640 DOM elements
var x, t = (new Date).getTime();
for (n=0; n < 50; n++) {
jQuery(':data(lalala)').each(function() {
x++;
});
};
console.log(((new Date).getTime()-t)/1000);
Since I don't need RegExp as parameter of :data selector I've tried to replace this by
var x, t = (new Date).getTime();
for (n=0; n < 50; n++) {
jQuery('*').each(function() {
if ($(this).data('lalala'))
x++;
});
};
console.log(((new Date).getTime()-t)/1000);
This code is faster (5 sec), but I want get more.
Q Are there any faster way to get all elements with this data key?
In fact, I can keep an array with all elements I need, since I execute .data('key') in my module. Checking 100 elements having the desired .data('lalala') is better then checking 3640 :)
So the solution would be like
for (i in elements) {
el = elements[i];
....
But sometimes elements are removed from the page (using jQuery .remove()). Both solutions described above [$(':data(lalala)') solution and if ($(this).data('lalala'))] will skip removed items (as I need), while the solution with array will still point to removed element (in fact, the element would not be really deleted - it will only be deleted from the DOM tree - because my array will still have a reference).
I found that .remove() also removes data from the node, so my solution will change into
var toRemove = [];
for (vari in elements) {
var el = elements[i];
if ($(el).data('lalala'))
....
else
toRemove.push(i);
};
for (var ii in toRemove)
elements.splice(toRemove[ii], 1); // remove element from array
This solution is 100 times faster!
Q Will the garbage collector release memory taken by DOM elements when deleted from that array?
Remember, elements have been referenced by DOM tree, we made a new reference in our array, then removed with .remove() and then removed from the array.
Is there a better way to do this?
Are there any faster way to get all elements with this data key?
Sure!, loop over the data store directly instead of via the element, for example if you wanted a count:
var x=0;
for(var key in $.cache) {
if(typeof $.cache[key]["lalala"] != "undefined") x++;
}
This will be nearly instant, since elements only have an entry in $.cache if they have data and/or events, and there's no DOM traversal happening.
For the other piece, yes this will skip removed elements, since their cache is cleaned up as well, provided you don't remove them via .innerHTML directly.
Since V1.4.3 jQuery supports the HTML5-data-attribute.
This means: if you set an HTML-attribute data-lalala you can also access these attribute using element.data('lalala') . This should be much faster, because you an use the
native attribute-selector $('*[data-lalala]') instead of some workarounds.
So you have to use:
el.attr('data-lalala', my_data);
instead of
el.data('lalala', my_data);
Note: As those data-attributes only allow strings, you'll need to store objects as a stringified JSON there, if you need to work with objects.
I have a web app where we would be inserting hundreds of elements into the DOM
Essentially, I'm doing
$('#some_element').html('<lots of html here>');
repeatedly. In some cases I might need to do $('#some_element').appendTo('more html');
From previous experience inserting html text using the append or setting the innerHTML of an element is slow.
I've heard that you can increase performance by first putting the elements in a DOM fragment and then moving its location to inside the element you want.
The performance of this is key. Do you guys have any tips or suggestions on maximizing the performance? Any hacks I can do to make this fast?
Edit: as mentioned in a comment: The app involves a real-time stream of various data, so it needs to be able to constantly add new DOM elements to represent the new data. (This might also lead to another problem of having too many DOM elements, so would need to elements that are too old).
Just don't do html() (or use its native cousin innerHTML = …) repeatedly. Pack your HTML in one variable and do html() only once (or as few times as possible). E.g.:
var buf = [], i = 0;
buf[i++] = "<p>"; /* or use buf.push(string) */
buf[i++] = "some text";
buf[i++] = "some other text";
buf[i++] = "</p>";
element.innerHTML = buf.join("");
Also see the Quirksmode entry on innerHTML and the W3C DOM vs. innerHTML page.
Update: Yesterday I found the amazing article When innerHTML isn't Fast Enough by Steven Levithan about his function replaceHtml, which is even faster than using just innerHTML, because it removes the DOM nodes that are to be replaced using standard DOM manipulation before innerHTML is used:
function replaceHtml(el, html) {
var oldEl = typeof el === "string" ? document.getElementById(el) : el;
/*#cc_on // Pure innerHTML is slightly faster in IE
oldEl.innerHTML = html;
return oldEl;
#*/
var newEl = oldEl.cloneNode(false);
newEl.innerHTML = html;
oldEl.parentNode.replaceChild(newEl, oldEl);
/* Since we just removed the old element from the DOM, return a reference
to the new element, which can be used to restore variable references. */
return newEl;
};
To clarify what Marcel said in his answer:
DOM manipulation (using appendTo, creating Element's, TextNode's, etc) is orders of magnitudes slower than just read/writing innerHTML.
Real time streaming should update not append. As mentioned, use a buffer. Another thing you could do is.
var buffer = [];
setInterval(function(){ $("#div").html(buffer.join(""); buffer = []; }, 1000);
buffer.push("html");
buffer.push("html");
Set the timeout to whatever you need and it will flush the buffer and append it.
Here is a function and interval you can use.
var buffer = [];
var wait = 1000; /* ms to wait, 1000 = 1 second */
var htmlId = "puthere"; /* Change this to the div ID */
setInterval(function(){
var tmp = buffer; /* Switch the buffer out quickly so we aren't scrambled
if you addToBuffer in the middle of this */
buffer = [];
document.getElementById(htmlId).innerHTML = tmp.join("");
}, wait);
addToBuffer = function(html){
buffer.push(html);
};
This is an odd one, for whatever reason, getting the children of an element doens't work in Camino browser. Works in all other browsers. Anyone know how to fix this? Google is no help :(
var site_result_content = document.getElementById(content_id);
site_child_nodes = site_result_content.children;
alert('started');
for(i=0;i<site_child_nodes.length;i++) {
alert('cycle1');
document.getElementById(site_child_nodes[i].id).className = 'tab_content';
ShowHide(site_child_nodes[i].id,'hidden');
}
In this case, the started alert is called, but the cycle1 isn't.
Use childNodes instead. children started out as a proprietary property that in IE, whereas childNodes is in the W3C DOM spec and is supported by every major browser released in the last decade. The difference is that children contains only elements whereas childNodes contains of all types, in particular text nodes and comment nodes.
I've optimized your code below. You should declare all your variables with var, including those used in loops such as i. Also, document.getElementById(site_child_nodes[i].id) is unnecessary: it will fail if the element has no ID and is exactly the same as site_child_nodes[i] otherwise.
var site_result_content = document.getElementById(content_id);
var site_child_nodes = site_result_content.childNodes;
alert('started');
for (var i = 0, len = site_child_nodes.length; i < len; ++i) {
if (site_child_nodes[i].nodeType == 1) {
alert('cycle1');
site_child_nodes[i].className = 'tab_content';
ShowHide(site_child_nodes[i].id, 'hidden');
}
}
I'd hazard a guess that it's not been implemented yet (it was only implemented in Firefox 3.5). You can use childNodes instead, which returns a list of nodes (rather than just elements). Then check nodeType to make sure it's an element.
var site_result_content = document.getElementById(content_id);
site_child_nodes = site_result_content.childNodes;
alert('started');
for(i=0;i<site_child_nodes.length;i++) {
// Check this is actually an element node
if (site_child_nodes[i].nodeType != 1)
continue;
alert('cycle1');
document.getElementById(site_child_nodes[i].id).className = 'tab_content';
ShowHide(site_child_nodes[i].id,'hidden');
}
//create an instance of the XML parser
if (window.ActiveXObject)
{
//Checking if the browser is IE
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false"; //make sure doc is fully loaded
xmlDoc.load(strPath) //load the file in the parser
if (xmlDoc.parseError.errorCode != 0)
{
alert("Error #: " + xmlDoc.parseError.errorCode;
}
}
//for mozilla based browsers
else if (document.implementation && document.implementation.createDocument)
{
xmlDoc= document.implementation.createDocument("","doc",null);
xmlDoc.async=false; //make sure doc is fully loaded
loaded = xmlDoc.load(strPath);
if(!loaded)
{
alert("Error in XML File");
}
}
//Parse the XML
var root = xmlDoc.documentElement;
level1Nodes = root.children;
for(var index1 = 0; index1 < level1Nodes.length; index1++)
{
//Extract the markup content from XML
var level1Node = level1Nodes[index1];
var strName = level1Node.children[0].textContent;
var strHeader1 = level1Node.children[1].tagName;
var strHeader1Content = level1Node.children[1].textContent;
}
Is the "children" property available in the IE DOM Parser?
In IE, an XML document does not implement the same document object model as an HTML document; in particular, XML Node objects don't have the children property, which is non-standard.
You should use the childNodes collection instead. However be aware that in Firefox and other browsers - and, IIRC, under very specific circumstances in IE - this collection will also include text nodes that contain only whitespace, such as line breaks in the original XML file. So you will need to check the nodeType property: if it has the value 1, it is an Element, and will have properties such as tagName.
Furthermore, as MSXML implements DOM Level 1, whereas Firefox implements DOM Level 3, you won't be able to use the textContent property, which was introduced in Level 3. Instead, you will have to iterate over the childNodes of nodeType === 3 and concatenate their nodeValue properties, and probably then will want to trim any leading or trailing whitespace. Alternatively, if you know that there will only ever be textNodes in there, call the normalize method of the element first to make sure it only has one text node child.
Nobody ever said this stuff was supposed to be easy :-(
children is an object in IE6. Perhaps there's an inconsistency in that IE's first child is a text node, whereas in other browsers the first child is a DOM element node? Usually you'd use .childNodes and .childNodes.length and check for .nodeType==1 in a loop to run through the children.