Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the fastest way to get a dom element?

I'm performance-tuning my code, and am surprised to find that the bottleneck is not dom node insert, but selection.

This is fast:

var row = jquery(rowHTML).appendTo(oThis.parentTable);

but the subsequent getting of an element inside "row" is slow:

var checkbox = jquery(".checkbox input", row);

I need to get the checkbox in every row so I can attach an event handler to it. Selecting the checkbox is ALMOST 10X AS SLOW as inserting the entire parent row.

What am I doing wrong here?

like image 913
morgancodes Avatar asked Apr 03 '09 15:04

morgancodes


2 Answers

DOM manipulation uses native functions to perform simple operations. Browser vendors optimize these. You are building the row from HTML. Internally jQuery is using .innerHTML to build the collection which then patches into the browser's mega-fast parser.

Selection is slow in comparison because JS code needs to loop through the DOM repeatedly. Newer browsers have native selection handling which provides dramatic speedups to selector based JS. As time moves on this will be less of a problem.

Here is how the query in question, $(".checkbox input", row), breaks down:

  1. row.getElementsByTagName('*');
  2. for-loop through every element returned (all elements within the row) and test elements[i].className with /(\s|^)checkbox(\s|$)/.
  3. for-loop every element still remaining and collect matched[i].getElementsByTagName('input');
  4. unique the final collection.

This is different for jQuery 1.3 as it's engine moves through the selector the other way around, beginning with getting all input elements and then testing the parent elements.

Rremember that the JS selector engines implement a lot more of the CSS selector spec than is actually usable with CSS (or implemented by current browsers). Exploiting this, and knowledge of the engines, we can optimize selector can be optimized in a few different ways:

If you know what element type the .checkbox is:

$("td.checkbox input", row);

It is faster for filter first for type and then for the class for only those matches. This doesn't apply for a very small subset of elements, but that is almost never the case in praxis.

The single class test is the slowest of the common selectors people actually use.

Simpler selection:

$("input[type=checkbox]", row);

One loop is faster than two loops. This only finds input elements and then directly filters them by type attribute. Since sub/child-elements are never used, unique may also be skipped (and smart engines will try to do this because unique is slow).

A more direct selector:

$("td:first.checkbox input", row);

A more complex selector may actually be faster if it is more direct (YMMV).

If possible, move the search context up to the table level:

By this I mean that instead of looping through the rows, and searching for the checkbox in every one, leave them alone until after the loop and then select them all at a time:

$("tr td:first.checkbox input", table);

The point of this is to eliminate the overhead of firing the selector engine up repeatedly, but instead do everything in one haul. This is presented here for completeness rather than something that I think would return massive speedups.

Don't select:

Build the row from bits, assigning events as you go.

var row = $( '<tr></tr>' );
var cell = $( '<td class="checkbox"></td>' ).appendTo( row );
$( '<input type="checkbox" name="..."/>' ).appendTo( cell ).click(/* ... */);

This may be impossible for reasons of Ajax or other templates out of your control. Additionally, the speed may not be worth turning your code into this sort of mess, but sometimes this may make sense.

Or, if none of these work for you, or return too performance gain, it may be time to rethink the method entirely. You can assign an event listener higher up the tree and grab the events there, instead of per-element instance:

$('table').change(function(e){
  // you may want a faster check...
  if ( $(e.target).is('input[type=checkbox]') ) {
    // do some stuff ...
  }
});

This way you don't do anything unless, and until, the user actually requests it. Fastest. :-)

like image 81
Borgar Avatar answered Nov 15 '22 09:11

Borgar


var checkbox = jquery(".checkbox input", row);

This is traversing the entire dom tree to find the checkbox. You could possibly speed it up by changing the selector to an ID which can use the browsers native getElementById functionality.

var checkbox = jquery("#checkbox input", row);

You could also use your row as a starting point for the DOM search like the following example. Now your not parsing through the entire DOM tree again to find the matched element.

var row = jquery(rowHTML).appendTo(oThis.parentTable);
row.children(".checkbox input");
like image 26
John Farrell Avatar answered Nov 15 '22 09:11

John Farrell