Sunday, August 14, 2005

Optimizing Client Side JavaScript: an example

I recently installed AppPerfect Code Analyzer, a free IDE plugin (I use it in NetBeans but it works in Eclipse, JDeveloper etc.). This tool scans your Java classes and advises you on improvements to make your code more efficient. What is nice about this tool is that for each problem it gives a short explanation of why the code is sub-optimal. It seems very keen on reducing the number of new object instantiations in loops. Although this is very definitely not a JavaScript optimisation tool it did give me some transferable ideas.

In previous blog entries I have described my feed aggregator application and how recently I was moving some of the functionality from the server side JSP to the client side JavaScript.

I wrote a particularly time consuming JavaScript function which was taking an unacceptably long time to process on the client side. The function traverses the HTML document looking for list items with a particular proprietary attribute and then conducts some processing based on the contents of a cookie and this attribute value. Lets concentrate on this single function, the first incarnation of this function looked like this:


function traverseDates(node, cookieDate) {
if (node.nodeName == 'UL'){
if(node.hasChildNodes()) {
for(var i=0; i< node.childNodes.length; i++) {
if (node.childNodes[i].nodeName == "LI")
{
for(var j=0;j<node.childNodes[i].attributes.length;j++)
{
if (node.childNodes[i].attributes[j].nodeName == "gofetch:date")
{
var linkDate = node.childNodes[i].attributes[j].value;
if (parseInt(linkDate) < parseInt(cookieDate))
{
node.childNodes[i].className="old";
}
}
}
}
}
}

}
if(node.nodeName != "LI")
{
if(node.hasChildNodes()) {
for(var i=0; i < node.childNodes.length; i++)
traverseDates(node.childNodes[i], cookieDate);
}
}
}

It has to be said it was taking an especially long time to process on Internet Explorer, Firefox seemed to be outperforming Internet Explorer in it’s HTML DOM processing. Document traversal is by nature recursive [teacher joke: in the dictionary it says recursion: see recursion]. Recursive functions are essentially loops and therefore anything that reduces the number of unnecessary object instatiations and DOM object lookups will improve performance; so with this I mind I modified the above function to become:


function traverseDates(node, cookieDate) {
var nodeName = node.nodeName;
var hasChildNodes = node.hasChildNodes();
if (nodeName == 'UL'){
if(hasChildNodes) {
var length = node.childNodes.length;
for(var i=0; i<length; i++) {
var currentNode = node.childNodes[i];
if (currentNode.nodeName == "LI")
{
var linkDate = currentNode.getAttributeNode("gofetch:date").value;
if (parseInt(linkDate) < parseInt(cookieDate))
{
currentNode.className="old";
}
}
}
}

}
if(nodeName != "LI")
{
if(hasChildNodes) {
var length = node.childNodes.length;
for(var i=0; i<length; i++)
traverseDates(node.childNodes[i], cookieDate);
}
}
}

This performs better than the first function, I’m not sure why I didn’t think of using getAttributeNode before but introducing this removed an unnecessary loop. Accessing document objects once and reusing these references also made a difference. Below is my final attempt:


function traverseDates(node, cookieDate) {
var hasChildNodes = node.hasChildNodes();
if(hasChildNodes) {
var nodeName = node.nodeName;
var length = node.childNodes.length;
if (nodeName == 'UL'){
for(var i=0; i<length; i++) {
var currentNode = node.childNodes[i];
if (currentNode.nodeName == "LI"){
var linkDate = currentNode.getAttributeNode("gofetch:date").value;
if (parseInt(linkDate) < parseInt(cookieDate)){
currentNode.className="old";
}
}
}

}
if(nodeName != "LI"){
for(var i=0; i<length; i++)
traverseDates(node.childNodes[i], cookieDate);
}

}

}

I’m not saying this is the most optimal solution but the performance in Internet Explorer is now acceptable, problem solved. If people are going to move serious processing into client side JavaScript then the need to optimize JavaScript is going to become more widespread. Maybe we even need a JavaScript optimizer tool that does more than trying to reduce the file size, there may be one out there but I've yet to find a good free one (please advise me if you know different).

0 comments: