Javascript Performance

So at work I had to fix the performance of an AJAX call. It was using a java library called AjaxAnywhere to completely replace a table. When it did that with thousands of rows it got REALLY slow.

It was simply replacing the table with innerHTML, and I figured if I changed it to using dom methods it would be faster. It didn’t due to what I’ll discuss in the third bullet point. But I learned a few other lessons about performance along the way.

1) Copy your array length to a local variable:

This is much slower:

for(var i = 0; i < someArray.length; i++) {
  // do something
}

than this:

for(var i = 0, il = someArray.length; i < il; i++) {
  // do something
}

2) Don’t use prototypes object creation functions in loops (I think prototype object creation works great for the small stuff, and is very legible). They’re too slow. I started out with this:

var tr = new Element("tr", {
  'class': 'unselectable',
  'id': idValue
});

but it turns out that prototype adding attributes via a hash (what Element.writeAttribute does) looks up the dom node each time, which is a performance drain. I changed it to this:

var tr = document.createElement("tr");
tr.className = 'unselectable';
tr.id = idValue;
 

3) Create a render queue. It took me a long to find this, but you’ll probably run into it no matter how fast your javascript runs. Generally web browsers wait until your javascript function exits to update the dom. If you’re adding thousands of objects your page can blank for seconds redrawing. What’s the answer? A render queue, which looks something like this (posting as a nice class as I don’t think there are enough examples like this):

var lgt = lgt || {};

lgt.RepetitiveTaskManager = function() {
};

lgt.RepetitiveTaskManager.prototype.renderQueue = Array();
lgt.RepetitiveTaskManager.prototype.rendererRunning = false;

lgt.RepetitiveTaskManager.prototype.enqueue = function(methodRef) {
  var renderQueue = this.renderQueue;
  renderQueue.push(methodRef);
   
  if(this.rendererRunning == false) {
    this.rendererRunning = true;
    setTimeout(this.renderer.bind(this), 1);
  }
}

lgt.RepetitiveTaskManager.prototype.renderer = function() {
  var renderQueue = this.renderQueue;
  var length = (renderQueue.length > 5 ? 5 : renderQueue.length);
  for(var i = 0; i < length; i++) {
    var functionCall = renderQueue.shift();
    functionCall();
  }
    
  if(renderQueue.length > 0) {
    setTimeout(this._renderer.bind(this), 1);
    return;
  }
  this.rendererRunning = false;
}


// first declare our renderer function
var callback = function(table, cellContents) {

  var tr = document.createElement("TR");
  var td = document.createElement("TD");
  td.innerHTML = cellContents;
  tr.appendChild(td);
  table.appendChild(tr);
};


// now add 1000 rows to a table

var taskManager = new lgt.RepetitiveTaskManager();

var table = document.getElementById("myTableId");
for(var i = 0; i < 1000; i++) {
  taskManager.enqueue(callback.bind(table, i));
}

Don’t forget bind is your friend in this case. For those without much experience with it, bind let’s you pass a function call with the appropriate variables to call the method with, without calling the method at that exact moment.

By using this render queue it will render 5 rows at a time on the page, and the table will seem to expand down the page as the user watches. This isn’t desirable in all case, but it’s generally better than having the user’s browser get unresponsive.