Thursday, April 15, 2010

Personal experience with Javascript and jQuery

I have been working on a jQuery based control library for a few months and today I replaced the embedded 1.3.2 version with the 1.4.2 version. I immediately noticed an increase in performance. Before, the profilers were screaming at the CLASS function, immediately followed by css. Now, the CLASS function seems to work 10 times faster. Some resizing script that I was using and was kind of sluggish now behaved all perky. I personally believe John Resig did some good things in the new 1.4 jQuery release.

Also, trying to optimize javascript code and having attended a Javascript training recently, I reached some conclusions that I want to formalize and place in a blog post, but just in case I don't find the resources, here are some quick pointers:
  • Javascript has function scope and closure support, which means that a variable defined in a function will be accessed faster than one that is outside or global, also that any function you create inside another function remembers all the variables declared in that scope. You should declare local variables in init functions and also declare local functions that use those variables, essentially guaranteeing faster access and that no one will overwrite them accidentally. Also, cache in this way the global variables and functions that are commonly used, like document
  • Cache the jQuery objects, not the elements. Not only it reduces the overhead of recreating them, but they "track" the underlying elements even if one removes/adds/modifies them.
  • Use the context format find method of the jQuery selector to get to child controls.
  • There are a lot of free profiling tools for javascript: dynaTrace Ajax is pretty cool, but also tools like the IE8 developer toolbar profiler or the famous Firebug and its many addons. You should use a profiler to understand where your bottlenecks are, they might not be where you expect them to be.
  • Redrawing the DOM elements is an expensive operation and changing the style of an element has many side effects. You should check if the value you wish to change in the style of an element is not already set. For example jQuery has the outerWidth and outerHeight functions that should return the same sizes as the value you pass to width and height (at least for divs with overflow not 'visible')

Here is some code that demonstrates the above rules:

/* set an element to be of the same size as another element */
function sameSize(base,target) {
// jQuery and cache
// local variable will be remembered
// and accessed faster
var lastSize={
// local function will be private and also accessed faster
function checkSize() {
// get the outer size of the base element
var baseSize={
// compare it with lastSize
// do nothing if this size was handled before
// use the !== inequality for extra speed (no type conversion)
if (baseSize.width!==lastSize.width
||baseSize.height!==lastSize.height) {
// get both the declared and real size of the target element
var targetSize={
// only change the size (and thus fire all sorts of events
// and layout refreshes) only if the size is not already
// declared as such or simply the same for other reasons
// like 100% layout in a common container
if ((targetSize.width!==baseSize.width
&&targetSize.declaredHeight!=baseSize.height+'px') {
// Javascript object notation comes in handy
// when using the css jQuery function
// cache the handled size
// bind the function to any event you want to
// since it will only do anything if the size actually
// needs changing

/* faster getElementById */
// use a local anonymous function as a closure
// cache the document in a private local variable
var d=document;
byId=function(id) {
return d.getElementById(id);
// a lot more code might come here, all using byId and accessing
// it faster since it is local to the closure
/* you might think that this doesn't do much if no other code
in the closure, since byId would be a global function and just as
slow to be accessed, but remember that you only need to find the
function once, while document.getElementById needs to find document,
and then getElementById. And document is a large object. */

/* find the tables inside a div using the context syntax */
var div=$('#myDiv');
var childTables=$('table',div);