Zach’s ugly mug (his face)

Zach Leatherman

Performance Caveat with jQuery Selectors and Live Events

08 May 2009 Zach Leatherman

Prerequisite: Knowledge/Experience with jQuery Live Events (new in jQuery 1.3), and the concept of Event Delegation.

When developing on the front end, it’s easy to prioritize correctness over performance. Performance is the step child that gets lost while you’re pulling your hair out worrying about cross browser compatibility. It’s very important to regularly benchmark your JavaScript code, using a profiler or some form of benchmarking code paired with a cross browser logging utility (see Firebug Lite, YUI Logger, or log4javascript).

Event delegation is a great way to program for performance. The live jQuery method was a great addition to the jQuery core, it makes event delegation really easy (see also the closest method). Unfortunately, it isn’t quite what I expected.

For example, say you have a page containing approximately 500 custom tooltip components on it (not typical, but stick with me, this is to prove a point). How might one go about adding a simple live event to activate each tooltip when the user hovers over it?

$('span.myTooltip').live('mouseover', function(event) {
    // activate tooltip
});

See the problem? jQuery will actually run the selector on the document, resulting in unnecessary overhead. jQuery is only assigning a single event handler to top level of the document, why does it need to know what nodes it will be binding to before assigning the callback?

What can we do? Let’s create a jQuery function, instead of a method, so it won’t query the document. Try this on for size:

$.live = function(selector, type, fn) {
    var r = $([]);
    r.selector = selector;
    if(type && fn) {
        r.live(type, fn);
    }
    return r;
};

Usage

// Single event type
$.live('span.myTooltip', 'mouseover', function(event) {
    // activate tooltip
});
 
// Multiple event types (you can call the jQuery live method on the return value from the function)
$.live('span.myTooltip')
    .live('mouseover', function(event) {
        // activate tooltip
    })
    .live('mouseout', function(event) {
        // deactivate tooltip
    });

Also, as a side note, keep in mind that jQuery live doesn’t support space separated events, like bind does.

// Will not work.
$('span.myTooltip').live('mouseover mouseout', function() {});

Have fun!

14 Comments

➡ Load Disqus to Leave a Comment ⬅

Very interesting. I never even thought of the overhead caused by the initial selection. A good point and definitely something that needs improving in the jQuery core!

I agree with your comments. If performance is critical I would advocate the use of the more traditional delegation method where you delegate an event to a closer contextual element than the document e.g container div or table etc and using the following format

if ( $(ev.target).is('a') )
.
IIRC on a small test I did it uses 50% less method calls performs 25% quicker than the live method. I think you can safely scale out these numbers on a large complicated dom.

Sure, removing the first selection can improve things during initialization, also I am not sure the way you are doing it is safe for all the tricks of jQuery internals.

Strictly speaking about the performances, you just scratched the surface of the problem and your cure is not enough (for performances, I repeat).

A "match(element, selector)" method is best suited than a "select(selector, from)" method for a fast event delegation. j

jQuery does not currently implement such a method, it does that by using a standard "select()" then look in the result set and see if the searched element is in there (slow/slow).

The event target should be passed to the "match(element, selector)" method. Only that element should be matched against the "selector", not all the elements in the document as it currently happens in most implementations.

With event delegation these selection operations have to be repeated continuously for most events while users interact with the pages. You can easily see the selection method itself is the real bottleneck here.

Zach Leatherman

23 May 2009 at 02:05PM

Great comments guys!

@redsquare

From my perspective, I want the code simplicity of being able to not perform a selector at all to improve both onload performance, and alleviate having to add event handlers later when I add new content using ajax. The live method (in it's current form) addresses only the latter, not the former.

If I used if($(event.target)), then I'd still have to perform SOME sort of selector to bind the callback to an element, instead of just using the document object.

Maybe I'm being super picky here, but in my tests, the performance difference is large enough to be picky about.

Zach Leatherman

23 May 2009 at 02:08PM

@Diego

In my stuff, there isn't too huge of a performance bottleneck on the callback execution. I'm a bigger stickler for onload performance. :)

I would agree that the "hack" presented above maybe isn't the most forward thinking approach. Perhaps a hybrid approach with what redsquare presented is in order.


$(document).bind('click', function(event)
{
if($(event.target).is(...)) {
...
}
});

This feels like a hack

$.live = function(selector, type, fn)
{
var r = $([]);
r.selector = selector;
if(type && fn) {
r.live(type, fn);
}
return r;
};

allthough it works and it improved performence on my page by reducing the number of calls with 60%.
But is this a preferable way of doing event delegation with jquery?

Zach Leatherman

09 Nov 2009 at 03:58PM

Depends on what event you're trying to delegate. I would look at the above comments, it seems like for the normal events supported by live, you can just as easily attach your own event handler to the document node and do it the least abstracted way. Which in this case, probably would be preferable.

After looking at how the .live() method is implemented in the jQuery source, I had been doing the following:

$.extend({ handle: function(type, selector, fn) {
return $.fn.live.apply({ selector:selector }, [type, fn]);
});

This is called as $.handle('click', 'blockquote', function(event) { … }); (read as handle the click event for any blockquote).

I came across your solution when I went to file a feature request (see existing ticket) and now I’m not sure which I prefer :P

Hopefully an officially supported method will materialise in jQuery 1.4.

Forgive me if I'm missing something obvious here...
Isn't the delegate function the right function for this job?

$('body').delegate(''span.myTooltip'', 'mouseover', function(event) {
// activate tooltip
});

My syntax was slightly wrong but you get the idea.

Thanks..

Zach Leatherman

23 Jun 2010 at 11:04PM

Hey Sam,
Not really, $('body') will still execute the selector query. We want to delay that as long as possible.

Zach Leatherman

07 Jul 2010 at 01:25AM

Actually, if you use $(document) or $(document.body), delegate should work very similar to the workaround included above.

Nice blog, thank you! I really love it!

Hi,
I am using jQuery 1.5 dhtmlxtabbar for tab component. When I use the tab component for very big application, have performance issues. Take more time to load the tab and their content, during this time tab header only displayed on that page, after long time tab is loaded. Component method has been called in javascript onload event. Anybody help me to solve this issue.