Live events is a new feature in jQuery 1.3. It could be used as an alternative to traditional
event delegation techniques. It provides a convenient way to implement event delegation. The following code will bind an event handler to the document. When events bubble up jQuery will check if any ancestor of the original target has the class myButton and delegate the event accordingly.
jQuery('.myButtton').live('click', function(){ //do stuff });
This is very convenient when your markup is loaded through ajax or created on the fly by JavaScript. The
jQuery documentation provides some more info on how it works.
Performance
Anytime the user clicks anywhere on the document an event will be triggered and jQuery needs to determine if it should delegate it or not.
jQuery will of course need some cpu time to do this check which could potentially result in sluggish behaviour when the user clicks on the page.
Testcases
To help me understand what performance tradeoff I make when using live events, I have set up a few test cases.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>jQuery live tester</title>
</head>
<body>
<div id="result1">waiting for results...</div>
<div id="result2">waiting for results...</div>
<div id="result3">waiting for results...</div>
<div id="result4">waiting for results...</div>
<div id="result5">waiting for results...</div>
<div id="result6">waiting for results...</div>
<div id="result7">waiting for results...</div>
<div id="button">Button</div>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
/*<![CDATA[*/
jQuery(document).ready(function(){
function measureClick(output, text) {
var target = jQuery('#button');
var count = 10;
var start = new Date();
for(var i=0; i<count; i++) {
target.click();
}
var stop = new Date();
output.html(text + ((stop.getTime() - start.getTime())/count));
}
function addEvents(count, selector, context) {
context = context || document;
for(var i=0; i<count; i++) {
jQuery(selector + i, context).live('click', function() {});
}
}
function removeEvents(count, selector) {
for(var i=0; i<count; i++) {
jQuery(selector + i).die('click');
}
}
jQuery('#button').bind('click', function() {});
measureClick(jQuery('#result1'), 'without live events: ');
addEvents(10, '.garbage');
measureClick(jQuery('#result2'), 'with 10 live events: ');
removeEvents(10, '.garbage');
addEvents(100, '.garbage');
measureClick(jQuery('#result3'), 'with 100 live events: ');
removeEvents(100, '.garbage');
addEvents(200, '.garbage');
measureClick(jQuery('#result4'), 'with 200 live events: ');
//Add large DOM-tree
for(var i=0; i<500; i++) {
jQuery('<div class="garbage' + i+'">garbage</div>').appendTo('body');
}
measureClick(jQuery('#result5'), 'with 200 live events and large dom: ');
removeEvents(200, '.garbage');
addEvents(100, '#garbage');
measureClick(jQuery('#result6'), 'with 100 live events with id selector: ');
removeEvents(100, '#garbage');
addEvents(100, '#something div.garbage td tr div div.test');
measureClick(jQuery('#result7'), 'with 100 live events with complex selector: ');
removeEvents(100, '#something div.garbage td tr div div.test');
});
/*]]>*/
</script>
</body>
</html>
Test results
test |
Firefox 3.5 |
IE8 |
IE7 |
Chrome |
Safari 4 |
without live events |
0.3 |
1.6 |
0.5 |
0.2 |
0.3 |
with 10 live events |
4.9 |
9.4 |
15.6 |
2.1 |
3.4 |
with 100 live events |
45 |
93.8 |
143.8 |
21.5 |
20.5 |
with 200 live events |
94.2 |
201.6 |
306.2 |
36.6 |
40.6 |
with 200 live events and large dom |
92.2 |
201.6 |
306.2 |
36.9 |
41.7 |
with 100 live events with id selector |
50.4 |
90.6 |
157.8 |
17.2 |
19.7 |
with 100 live events with complex selector |
109.1 |
231.3 |
373.4 |
46.9 |
48.4 |
The tests were performed using jQuery v1.3.2 on a Pentium4 2.8GHz with 2GB RAM running win xp
Explanation of tests
Basically the tests are done by registering a number of live events and one normal event. We measure the time it takes to process the normal event to see how it gets impacted by the live events. First we measured the time it takes to handle the normal event without any live events registered. This is just to have something to compare against. The result is just a fraction of a millisecond. Next we add 10 live events to see how this impacts the performance. The event now takes 4.9 milliseconds to process in FireFox 3.5. To push it a bit we add more live events. We then see that 100 live events results in ca 45 milliseconds and 200 live events results in ca 94.2 milliseconds. It seem like the performance impact is approximately linear when adding more live events. What about dom size? In the first tests the markup is very simple with a very small dom-tree. A bigger dom-tree might have some impact on the performance. To test this we simply add 500 div’s to the dom and test again. This is what test no 5 does. Surprisingly we see that the performance impact of the live events does not get worse when the dom size increases. What about different types of selectors? One might also suspect that the performance is depending on the type of selector used. In test 3 a simple class selector was used. Test 6 instead uses an id selector and test 7 uses a more complext selector. We see that there is no big performance difference between a simple id selector and a simple class selector. However when using more complex selector the performance impact seems to be worse.
Conclusion
The tests show that adding live events will have some performance impact. As long as the number of registered live events is small the impact is not significant. However if you have a complex site with many events you may run into trouble, especially if you need to support browsers with poor JavaScript performance.
What could be done to improve this?
In my view it would have been better if jQuery would let you somehow define what element you want to register the delegating event on, so that the impact is limited to a specific context. The default could still be to add it to the document. This could be done in at least two different ways. One option could be to add the delegating event to the context given in the core jQuery(expression, context)
function. I dont know enough about the internals of jQuery to say how feasible this is.
Another option could be to redefine the live method as follows:
jQuery('<where_to_attach_the_event>').live( '<delegation_selector>', eventType, eventHandler);
so the current live behaviour would be achieved by
jQuery('document').live('<delegation_selector', eventType, eventHandler)
Update:
After a comment from Jonathan Sharp I added some more tests to see how a more nested dom-tree affects the performance. When adding 30 nested divs we can see that a click deep down the tee will be affected more by the live events.
var button = jQuery('<div id="button">button<div>').appendTo('body');
button.bind('click', function() {});
addEvents(100, '.garbage');
//Add nested DOM-tree
var element = jQuery('body');
for(var i=0; i<30; i++) {
element = jQuery('<div></div>').appendTo(element);
}
measureClick(jQuery('#result1'), 'click outside of nested elements: ');
button.appendTo(element);
measureClick(jQuery('#result2'), 'click inside of nested elements: ');
In this test I add 30 nested divs to the dom-tree and I add 100 live events. In firefox the output of this test is:
click outside of nested elements: 27.2
click inside of nested elements: 251.9
This shows that when I click on an element that is a direct child of the body, the performance is ok, but when I click an element deep down the nested divs the performance impact is much worse.
This makes it even more desirable to be able to define the context when registering a live event.
Update 2 – jQuery 1.4a1
On request I have run the same tests as above on the same machine but with the new alpha release of jQuery 1.4. The results are as follows:
test |
Firefox 3.5 |
IE8 |
Chrome |
Safari 4 |
without live events |
0.3 |
1.6 |
0.2 |
0.3 |
with 10 live events |
4.5 |
9.4 |
2.1 |
2.7 |
with 100 live events |
38.5 |
79.8 |
19.6 |
19.9 |
with 200 live events |
73.9 |
162.7 |
40.8 |
39.6 |
with 200 live events and large dom |
79.5 |
167.3 |
42.2 |
39.9 |
with 100 live events with id selector |
37.7 |
90.7 |
18.9 |
20.1 |
with 100 live events with complex selector |
91.2 |
172 |
31.8 |
45.7 |
The test are not very precise. Running the tests twice will not produce the exact same results. It is clear though that the performance has been improved slightly.