Zach’s ugly mug (his face) Zach Leatherman

Performance Caveat with jQuery Selectors and Live Events

May 08, 2009

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!


< Newer
Quick Performance Tip: jQuery and addClass
Older >
ALARMd Unix Time Format for 1234567890 Day

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at IndieWeb Avatar for https://fontawesome.com/Font Awesome and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 84 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

14 Comments
  1. James Disqus

    08 May 2009
    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!
  2. redsquare Disqus

    11 May 2009
    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 formatif ( $(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.
  3. Diego Perini Disqus

    11 May 2009
    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. jjQuery 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.
  4. Zach Leatherman Disqus

    23 May 2009
    Great comments guys!@redsquareFrom 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.
  5. Zach Leatherman Disqus

    23 May 2009
    @DiegoIn 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(...)) { ... }});
  6. Jakob Disqus

    09 Nov 2009
    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?
  7. Zach Leatherman Disqus

    09 Nov 2009
    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.
  8. Dave Cardwell Disqus

    10 Nov 2009
    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 :PHopefully an officially supported method will materialise in jQuery 1.4.
  9. Sam Blowes Disqus

    23 Jun 2010
    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});
  10. Sam Blowes Disqus

    23 Jun 2010
    My syntax was slightly wrong but you get the idea.Thanks..
  11. Zach Leatherman Disqus

    24 Jun 2010
    Hey Sam,Not really, $('body') will still execute the selector query. We want to delay that as long as possible.
  12. Zach Leatherman Disqus

    07 Jul 2010
    Actually, if you use $(document) or $(document.body), delegate should work very similar to the workaround included above.
  13. William Colvin Disqus

    10 Nov 2010
    Nice blog, thank you! I really love it!
  14. Sujatha Disqus

    03 Jun 2011
    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.
Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)