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