Paul Irish

Making the www great

On jQuery's Live()

Since the release of 1.4, there’s been a lot of discussion about live(). Now that it’s almost as fully featured as bind(), it’s getting quite a workout.

One of the larger calls to action is to introduce $.live(selector,eventType,handler). There are two primary reasons I’ve seen behind this proposal:

  1. There is a performance hit for the initial selection in $('a.whatever').live(.. that isn’t neccessary
  2. live() cannot work on any arbitrary collection of DOM elements in jQuery object, as a side effect of a very different internal operation, thus providing an inconsistent developer experience than all other $ methods.

$.live for performance

Sizzle is used to find any matching elements before the live method discards them an only uses the jQobj.selector property and context. It’s a waste, I agree. But you can avoid it. Zachleat posted this technique first. Then I stole a code snippet from @janl to show off in my jQuery Performance talk for jQCon. This inspired furf’s patch. And now jmar777 has resurrected that discussion.

I do not think the performance gain in $.live is worth adding it to jQuery core, by any means. Let’s take an example.

Let’s say we’re dealing with $(selector).live('click',fn) On every click on the page, the event is caught at the context.. the document. But points to the top-most element that was clicked on. jQuery then traverses from through each ancestor of that node all the way up to document checking if (jQuery(elem).is(selector)).[source] I think a reasonable estimated depth of evt.targets is like 8 (From the document to HTML element to BODY and so on.. a depth of 20 is not uncommon by any means). And lets say we have an average 5 clicks on a page. That’s 40 total checks against the selector (plus the original one done at bind time). So in my opinion a 1/41 speed gain isn’t worth changing the API.

Scoping the delegation a context, however has a much larger impact on performance: $(selector,elem).live('click',fn) Here, only clicks within the context are caught, so not only is the parent traversal depth much more shallow, but that entire sequence is avoided for all events occurring outside that area.

The current API to take advantage of this optimization requires code like $('a.awesome', $('#box')[0]).live(... Yuck! So I wrote a patch to deliver this same optimization to any ID-rooted selectors. (The perk being that everyone who learns #id-rooted selectors are fast for selecting get a related perf boost here). But as Justin Meyer pointed out in the comments, it’s not as flexible as live() currently is.

$ cannot operate on any jQuery-wrapped collection of elements

$('').filter('.bar').live(... // fails
$('').parent().nextAll().live(... // fails
$(this).live(... // fails

Quite simply, $ needs to be at the top of a chain to guarantee the .selector and .context properties of the jQuery object represent that set. Unlike other $.fn methods, live utilizes those properties and not the actual collection of elements that result from selection, traversing, and filtering.

$.live() and let $.die()

What are the possible solutions?

  1. Turn on a dirty flag in traversal methods, so if you try and live() that collection, it will throw an error.
  2. Deprecate $ and use $.live(selector[,context],type,handler)
  3. Use $.live to create a “live jQuery object” that defaults to live-bindings: $.live(selector,context).click(fn).mouseenter(fn).unbind("click") [source]
  4. Change $ to operate from a context: $(context).live('selector', event, fn) This bakes the performance benefit from above right in.

Any others? What do you guys think? Is there a solution that preserves backwards-compatibility while solving the issue?

btw- thx to jmar777, yehuda katz, ben alman, ajpiano, brandon aaron and julian aubourg for their great thoughts on the issue.

The people in this discussion got together and hashed out what would make sense. John Resig then landed a new delegate/undelegate API. It’s good. :)