Event Delegation in jQuery and Performance (live vs bind vs other options)

Back home in Massachusetts for the holidays.  Lots of snow and lots of cold.  And an uncommon spell of morning quiet time in which, while trying to think of a better way to capture events in Google Analytics, I ended up comparing and testing different options for handling events, most involving delegation and most involving jQuery. (If you’re unfamiliar with the concept of event delegation, check out this post by Nicholas Zakas.)

For this purpose the goal is to listen for all click events and then decide which ones we want to send to Analytics.  The methods I compared were 1) using jQuery’s .bind() to bind all link elements; 2) using jQuery’s .live() for event delegation; 3) binding the click event to the document and then using .is() to match the links; 4) using standard event delegation with no jQuery at all.  Additionally, I tested .live() with jQuery 1.4a, which is supposed to include improved .live() performance.

The HTML for the test page consisted of 500 link elements, each nested 5 divs deep.

<div><div><div><div><div><a href="#1">Link #1</a></div></div></div></div></div>
<div><div><div><div><div><a href="#2">Link #2</a></div></div></div></div></div>
...

Normal event handling with .bind()
window.onload = function(){
  $("a").bind("click", function(event) {
    //If it's a link
    console.log("link!");
 
    //Prevent default action
    event.preventDefault();
  });
};
Event delegation with .live()
window.onload = function(){
  $("a").live("click", function(event) {
    //If it's a link
    console.log("link!");
 
    //Prevent default action
    event.preventDefault();
  });
};
Event delegation binding document and using .is()
window.onload = function(){
  $(document).bind("click", function(event) {
    //Is it a link?
    if ($(event.target).is("a")) {
      console.log("link!");
    } 
 
    //Prevent default action
    event.preventDefault();
  });
};
Event delegation without jQuery
window.onload = function(){
  document.onclick = function(event) {
 
    //Cross-browser event object
    event = event || window.event;
    //Cross-browser event target
    var target = event.target || event.srcElement;
    //Get target element type
    var targetElement = target.nodeName.toLowerCase();
 
    //Is it a link?
    if (targetElement == "a") {
      console.log("link!");
    }
 
    //Cross-browser prevent default action
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  };
};
Firebug profile results
Event .bind() .live() .bind()/.is() .live() jQuery 1.4a1 no jQuery
onload 54.49ms,
4186 calls
13.86ms,
206 calls
9.62ms,
182 calls
16.53ms,
200 calls
0.02ms,
1 call
10 matching click events 9.60ms,
100 calls
12.34ms,
470 calls
8.22ms,
250 calls
14.80ms,
330 calls
0.80ms,
10 calls
10 non-matching click events 0ms,
0 calls
18.73ms,
1310 calls
8.47ms,
250 calls
20.57ms,
1140 calls
0.25ms,
10 calls
Combined 64.21ms,
4436 calls
47.56ms,
2136 calls
26.30ms,
682 calls
51.90ms,
1670 calls
1.08ms,
21 calls

(Firefox 3.5.6, Mac OSX 10.4)

Note: the .is() and plain Javascript options assume that the event target is the element with which you want to work. .live(), which uses jQuery’s .closest() method, provides extra functionality by walking up the DOM to match possible ancestors. So on one hand you could call this an apple-and-oranges comparison. On the other, .live() is still a common solution for those only looking to capture the event target, and to that extent I think it’s a fair and worthwhile comparison.

The advantages of event delegation and of .live() over .bind() have been thoroughly discussed elsewhere.  And of course there are considerations beyond what’s represented in Firebug’s profiler.  So I’m not sure there’s much to say about that.

The document .bind()/.is() still has some of the jQuery overhead with the document query but overall it comes in quite ahead of .bind() and .live().

jQuery 1.4a’s .live() executes in fewer function calls than 1.3.2’s .live() but the timing is no better.  In these tests at least it’s actually slightly worse.

Performance-wise, the Javascript option wins by a mile.  If you’re not using a library and if you don’t need the event object normalizing that a library offers, you might consider going with the regular Javascript.  But chances are you’re already using a library for something, and in that case something like the .bind()/.is() compromise may be the best option.

Of course, we’re talking about very thin margins, margins unlikely to have an observable effect on your site.  And your page probably won’t have 500 elements you need to match.  But hopefully there’s something in these numbers that might inform other decisions related to event handling and performance.

Happy Holidays!

Both comments and pings are currently closed.

Discussion

Great article. In any case understanding need where to use each method .

Interesting article Rob!

really interesting comparison of the different ways for event handling. I really like the idea of event delegation and so I’ll will this method for future projects.

I’d love to see how jQuery’s new delegate() would measure up in this test. It addresses much of what’s wrong with live(). Any chance of a rematch?

I’d like to see onclick in the element added to this test