Mike West — Web Application Developer

Accessing and manipulating the DOM is slow. The speed of your JavaScript execution will very often depend entirely on the number, and the nature, of DOM calls that you make.

For example, a common problem when writing unobtrusive JavaScript is to loop through a group of elements (say, each cell in a table), doing something interesting to each in turn. An initial pass at this code might look something like:

var theTable    = document.getElementById('sought_after');
var rows        = theTable.getElementsByTagName('tr');
for (var rowNum=0;rowNum<rows.length;rowNum++) {
    var cells   = rows[rowNum].getElementsByTagName('td');
    for (var colNum=0;colNum<cells.length;colNum++) {
        var theValue = cells[colNum].innerHTML;

        //
        // do some processing here.
        //
    }
}

The code’s fine, and on small data sets, it’ll run quickly enough. That said, “quickly enough” is relative, and if the interesting something that you’re doing to each cell takes much time, then you’ll quickly find yourself running into performance problems.

One quick way to squeeze a few milliseconds of processing time is to avoid the expensive getElementsByTagName call inside the rows loop. Instead of grabbing a list of the cells in each row as we pass, we can grab all the cells, and process them as a flat array:

var theTable    = document.getElementById('sought_after');
var cells       = theTable.getElementsByTagName('td');
for (var cellNum=0;cellNum<cells.length;cellNum++) {
    var theValue = cells[cellNum].innerHTML;

    //
    // do some processing here.
    //
}

This code avoids the overhead of the internal for loop, and additionally replaces rows.length DOM calls with a single, larger call. The performance increase is non-trivial (and it’s even faster if you replace the for loop with the native Array.forEach method that newer versions of Firefox provide. Take a look at Dean Edward’s Enumerating JavaScript Objects for ideas in that regard). The downside is that we no longer have access to the rowNum and colNum variables that were used in the initial algorithm. If we want to do some calculations that require knowing where we are in our dataset, we’ll run into problems.

Simple arithmetic, however, can give us that information. In essence, what we’ve done to our dataset is a transformation of a table that looks like:

1   2   3   4   5
6   7   8   9   10
11  12  13  14  15

into an array that looks like:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15

If we know how many columns were in the original table (5), then we can use division and modulus to calculate the mapping between the cell array, and the position in the table:

var numCols = 5;
var colNum  = cellNum % numCols;
var rowNum  = Math.floor(cellNum / numCols);

Of course, that requires a modulus, a division, and a call to Math.floor. We can drop out the latter two in favour of a less-expensive if by simply incrementing rowNum at loop’s end when colNum is equal to numCols - 1. The whole thing, then, looks like:

var theTable    = document.getElementById('sought_after');
var numCols     = theTable.getElementsByTagName('tr')[0].childNodes.length;
var cells       = theTable.getElementsByTagName('td');
var rowNum      = 0;
for (var cellNum=0;cellNum<cells.length;cellNum++) {
    var colNum = cellNum % numCols;

    //
    // do some processing here.
    //

    if (colNum == (numCols-1)) { rowNum++; }
}

Mike West is a web application developer living in Munich, Germany. Professionally programming for the web since 2000, he's available for contract work now a web developer at Yahoo! Germany. Read Mike's bio, or drop him an e-mail.

Recent Articles


Comments

I typed this out before but I guess I forgot to submit it after previewing.

What about the DOM1 rows and cells properties of tables (or table section) and rows? I mean, there’s no need to do an getElementsByTagName(‘tr’) as that collection is allready right there on the table object, just use theTable.rows for a reference to al l the TRs and theRow.cells for a reference to all the TDs in a TR.

I guess that might speed things up a bit as well. Or is there something fishy with using DOM1 properties I don’t know about?

http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html#ID-64060425

Posted By: Jaap 31. August 2006, 20:43

The easy answer to that question is that I forgot about them.

I’ll run some benchmarks tomorrow to see if those collections are faster than the DOM calls I’m using. I suspect that you might be on to something here… :)

Posted By: Mike West 31. August 2006, 21:18

Comments Closed

Comments are closed after 6 weeks to avoid the icky comment spam we all loathe. Sorry if you missed the opportunity, but you can always drop me an e-mail!

flickr Photostream

Yahoo!'s Munich Office
atom:title
atom:title
atom:title