Event delegation vs direct binding when adding complex elements to a page - javascript

I have some markup like this (classes are just for explication):
<ol id="root" class="sortable">
<li>
<header class="show-after-collapse">Top-Line Info</header>
<section class="hide-after-collapse">
<ol class="sortable-connected">
<li>
<header class="show-after-collapse">Top-Line Info</header>
<section class="hide-after-collapse">
<div>Content A</div>
</section>
</li>
</ol>
</section>
</li>
<li>
<header/>
<section class="hide-after-collapse">
<ol class="sortable-connected">
<li>
<header/>
<section class="hide-after-collapse">
<div>Content B</div>
</section>
</li>
</ol>
</section>
</li>
</ol>
That is, nested sortable lists. The sortable plugin suffices, however, since each li (hereafter "item") maintains its level, though the inner lists are connected. The items have an always-visible header and a section visible when in expanded state, toggled by clicking the header. The user can add and remove items from either level at will; adding a top-level item will include an empty nest list inside it. My question is with respect to JS initialization of the newly created item: While they will share some common functionality, which I can cover via
$("#root").on("click", "li > header", function() {
$(this).parent().toggleClass("collapsed");
});
and
li.collapsed section {
display: none;
}
(Side question: would this be an appropriate place to use the details/summary HTML5 tags? It seems sort of iffy about whether those will even make it into the final spec, and I want a sliding transition, so it seems like I'd need JS for that anyway. But I throw the question to the masses. Hello, masses.)
If the root list is the only (relevant) element guaranteed to be in existence at page load, for .on() to work effectively, I have to bind all the events to that element and spell out the precise selector for each, as I understand it. So, for example, to tie separate functions to two buttons right next to each other, I'd have to spell out the selector in full each time, à la
$("#root").on("change", "li > section button.b1", function() {
b1Function();
}).on("change", "li > section button.b2", function() {
b2Function();
});
Is that accurate? That being the case, does it make more sense to forgo .on() and bind my events at the time the new item is added to the page? The total number of items will probably number in the dozens at most, if that makes a difference in the response.

You will create less CPU overhead in binding the events using $(<root-element>).on(<event>, <selector>) since you will be binding to a single "root" element instead of potentially many more single descendant elements (each bind takes time...).
That being said, you will incur more CPU overhead when the actual events occur as they have to bubble up the DOM to the "root" element.
Short-story: delegate saves CPU when binding event handlers; bind saves CPU when events trigger (e.g. a user clicks something).
So it's up to you to decide what point is more important for performance. Do you have available CPU when you add the new elements? If so then binding directly to the new elements would be the best for overall performance however if adding the elements is a CPU intensive operation you will probably want to delegate the event binding and let the event triggering create some extra CPU overhead from all the bubbling.
Note that:
$(<root-element>).on(<event>, <selector>, <event-handler>)
is the same as:
$(<root-element>).delegate(<selector>, <event>, <event-handler>)
and that:
$(<selector>).on(<event>, <event-handler>)
is the same as:
$(<selector>).bind(<event>, <event-handler>)
.on() is new in jQuery 1.7 and if you are using 1.7+ then .delegate(<selector>, <event>, <event-handler>) is just a short-cut for .on(<event>, <selector>, <event-handler>).
UPDATE
Here is a performance test showing that it is faster to delegate event binding than to bind to each element individually: http://jsperf.com/bind-vs-click/29. Sadly this performance test has been removed.
UPDATE
Here is a performance test showing that event triggering is faster when you bind directly to elements rather than delegate the binding: http://jsperf.com/jquery-delegate-vs-bind-triggering (Note that this isn't a perfect performance test since the binding methods are included in the test, but since delegate runs faster on binding it just means that bind is even faster relatively when talking about triggering)

Since the accepted answer has inaccurate tests (BTW: test your code, measure performance, don't just blindly follow some "rules" - this is not how optimization is done!) and is simply wrong I post fixed tests:
https://jsperf.com/jquery-delegate-vs-bind-triggering/49
which proves on such simple example there is NO difference between delegation or direct binding
The only cases when delegation is always bad are events like mouse move and scroll - which are triggered x times per second. THIS is where you will notice any performance difference.
If you have even 1ms difference (won't happen, but this is just an example) on single event - like click - you won't notice that. If you have 1ms difference on event that happens 100 times in a second - you will notice CPU consumption.
Just having thousand of elements won't negatively impact your performance with delegation - actually - this is the case when they should be used - to avoid hogging CPU when attaching thousand of event handlers.
So if you really need a rule to follow (don't do that) - use delegation on everything except mouse move, scroll and other events that you can expect to fire continuously.

Related

Which is better: using onclick or find element that triggered the event for handling events in jQuery?

I always wondered which is the better way of handling events in terms of code manageability, cleanliness and code reuse.
If you use the former then say a list of 10 anchor tags with click handler will have something like:
Click Me
Click Me
Click Me
... 10 times
which looks kind of odd.
With the latter method, using anonymous function, it'd be like:
$('a').on('click', function(e){});
At the end of the day, every event is bound to some element in the DOM. In the case of .bind, you're binding directly to the element (or elements) in your jQuery object. If, for example, your jQuery object contained 100 elements, you'd be binding 100 event listeners.
In the case of .live, .delegate, and .on, a single event listener is bound, generally on one of the topmost nodes in the DOM tree: document, document.documentElement (the element), or document.body.
Because DOM events bubble up through the tree, an event handler attached to the body element can actually receive click events originating from any element on the page. So, rather than binding 100 events you could bind just one.
For a small number of elements (fewer than five, say), binding the event handlers directly is likely to be faster (although performance is unlikely to be an issue). For a larger number of elements, always use .on.
The other advantage of .on is that if you add elements to the DOM you don't need to worry about binding event handlers to these new elements. Take, for example, an HTML list:
<ul id="todo">
<li>buy milk</li>
<li>arrange haircut</li>
<li>pay credit card bill</li>
</ul>
Next, some jQuery:
// Remove the todo item when clicked.
$('#todo').children().click(function () {
$(this).remove()
})
Now, what if we add a todo?
$('#todo').append('<li>answer all the questions on SO</li>')
Clicking this todo item will not remove it from the list, since it doesn't have any event handlers bound. If instead we'd used .on, the new item would work without any extra effort on our part. Here's how the .on version would look:
$('#todo').on('click', 'li', function (event) {
$(event.target).remove()
})
Second method is preferrable, since we should not be mixing our JavaScript with the HTML. (Separation of Concerns) . This way your code is kept clean.
This also works well with dynamically inserted HTML code.
`$('a').on('click', function(e){});` // Using jQuery.
Using Vanilla JS:
document.getElementById("idName").addEventListener("click", function(){}); // Bind events to specific element.
document.addEventListener("click", function(){}); // Bind events to the document. Take care to handle event bubbling all the way upto the document level.

Attaching event listeners using OnClick= or with script

Although it has already been asked, I want to adress the issue of correct jQuery programming.
Method #1:
<script>
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
VS
Method #2:
<script>
$(function() {
$("#ActionButton").on("click", DoClickAction);
}
function DoClickAction() {
// Some work
return false;
}
</script>
Do some work
I'm having a discussion with my colleagues about this, and my opinion is that both methods have enough pro and cons to not be able to say "this is the right way", but if I have to choose I tend to prefer Method #1, this is why:
Method #1 pros:
When debugging someone else code, you can easily follow which jQuery code is executed when somebody presses the link.
When you dynamically load (AJAX call) the content, it will always work, no need to rebind your jQuery events.
Method #2 pros:
It will produce less HTML code for the browser to download, because the script file will be cached and the onclick attribute is not necessary. Although this example uses more code.
You can re-use the code easily by using the same attributes, although using the onclick with 1 function is kind of the same thing.
What are your thoughts on this?
Instead of listing the pro's of either method, let me focus on the con's of method 1:
Change a function name == change the entire markup
All event handlers reside in the global scope. Working with closures can be a bit of a pain, then.
adding new elements dynamically (through JS or via ajax response) means that you'll either have to parse the markup and add the attribute one by one, or you'll have to send markup containing, essentially, JS function calls. Not safe, not clean
Each attribute is a new listener. The more attributes you have, the heavier the event loop will become
Mixing JS and HTML is not considered good practice. Think of it as separation of concern. The markup is there to provide the client with a UI. JS's job (in a browser) is to enhance the user experience. They have to work together, but have different tasks. Hence, they should be treated as separate entities.
As far as the second method goes, the only "cons" I can think of are:
Your code is slightly harder to understand, but if somebody can't work out what an event listener is, he shouldn't be working on your code, IMO.
Debugging can be harder, and older browsers might leak (jQ does contain an awful lot of X-browser related code, so it doesn't apply here. It does when you're writing vanillaJS)
In addition to this, method2 has another major pro, that you've not listed: delegation. At first, delegation looks hard, but It's easy, jQuery's $.delegate makes it easier, still, using $.on with a selector also delegates the event.
Basically, delegation allows you to deal with all events, for example click, for the entire page, or a section of the page, using a single listener. This as opposed to binding the event to each and every element. Thus: 1 listener on the event loop versus tens/hundreds. It's pretty obvious which is the more performant way of doing things.
Suppose you have a navigation div on a page, that looks like this:
<div id='nav'>
<ul>
<li id='nav-home'>Some pseudo-link</li>
<li id='nav-page1'>Another</li>
</ul>
</div>
You want to pick up on the user, clicking one of the <li> tags. The first method you listed makes for a right mess: <li id='nav-home' onclick='clickNav(event, this)'>. I'm passing the event object and this (a DOM reference) to have access to everything delegation gives me access to.
Using delegation, I can simply do this:
//jQ
$('#nav').on('click','li',function(e)
{
$.ajax({//you know the gist
url: 'ajax/' + $(this).id().replace('nav-',''),
success: function(){}
});
});
//vanillaJS:
document.getElementById('nav').addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (e.tagName.toLowerCase() === 'li')
{
//perform ajax call
}
},false);
I myself am very much partial to #2, as it provides a clean separation of JavaScript and HTML. The negatives of not having the action of a button be immediately visible in the HTML can be completely negated by browser plugins.
Furthermore, as you've already stated, sometimes I want to attach an onclick event to, say, every row of a table, and setting the OnClick attribute of an element on every row is much more wasteful than simply attaching a click handler to each of them with a single line of code elsewhere.

Best practice to avoid memory or performance issues related to binding a large number of DOM objects to a click event

First of all, I am testing various approaches using Chrome Dev Tools but I don't consider myself 'expert' with JS in todays modern browsers so I'm looking for additional feedback to compliment my testing.
I have a page which will regularly have 600+ elements that need to handle click events. I can think of at least 3 fairly different ways to approach this but I'm thinking about page size, speed, JS memory issues, object count-related issues (in terms of both performance and stability).
Include onClick="foo(this);" for each of the elements and define the function in one of my included .js files. -- Larger page, function defined once, I would think a smaller memory footprint for the JS, but larger for the page as a whole.
Use jQuery and $(selector).click(foo(this)); - Smaller page, function defined once, I'd think a larger memory footprint for the JS but smaller page overall.
Use jQuery and $(selector).click(function(this) { }); - Smallest page, function defined once, but I expect this to be the most demanding in terms of memory (and I'm concerned I might hit some obscure issue with jQuery or JS as a whole) but conceptually the most elegant.
I must support just about every browser one might expect jQuery to run in. The number of items could increase even more (possibly by a factor of 4 or 5).
If there's another approach that would be better, I'd love to hear it. If anyone wants to educate me on any advantages/pitfalls of my 3 approaches, I'd really appreciate that too.
The most efficient way to bind a large number of similar elements to a single event is to make use of event bubbling. You attach a single event handler to a common parent object and then, in the single event handler, you examine which object the event originated in to see if the original object is an object you're monitoring for this event.
For attaching the event, it costs you only a single event handler and you can serve an infinite number of child objects from that one event handler.
There's a slight performance degradation at the run-time of each event (probably not noticeable) because the event has to bubble up to a parent before the event handler sees it and the event handler has to check if the source object is a desired target object or not. But, it's massively more efficient to install the one single event handler rather than installing thousands of individual event handlers.
Delegated event handling also has the advantage that it works well for dynamically created objects, even objects created after the event handler was installed - something that doesn't not work well for event handlers installed directly on the objects themselves (non-delegated event handlers).
In jQuery, you can use delegated event handling like this:
$(common parent selector).on('click', selector of target objects, function() {});
For example, HTML:
<div id="parent">
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
<button class="addButton">Add</button>
</div>
Code:
$("#parent").on('click', ".addButton", function(e) {
// the value of `this` is set to the object that originated the event
// e.g. what was clicked on
});
Look at the delegated version of the "on" event handler, described here
http://api.jquery.com/on/
In the section titled "Direct and Delegated events"
You should use jQuery's on() method for this. On allows you to attach a handler to a high level DOM node (such as document) and rely on event bubbling to execute a handler when the event is triggered.
$(document).on('click', [selector], [data], handler);
You could do something simple like this in native js.
<div id="test">
<input type="button" value="Button 1"/>
<input type="button" value="Button 2"/>
<input type="button" value="Button 3"/>
</div>
<script>
var parent = document.getElementById('test');
parent.onclick = function(e){
alert(e.target.value);
};
</script>

removeClass of Mootools is very slow on IE

I am applying addClass and removeClass on same set of elements in mooTools.addClass works fine but removeClass takes too long approx 6-7sec.Please see the code undeneath.
$('usermailbox').getElements('input[class=emailmessages]').each(function(el){
el.getParents().addClass('unChecked');//works fine
});
$('usermailbox').getElements('input[class=emailmessages]').each(function(el){
el.getParents().removeClass('selected'); //Takes too long in IE
});
Do I have any hope ?
Right. Several issues here... Obviously, w/o seeing the DOM it is a little difficult to determine but:
you do the same operations twice. in the same instance you can addClass and removeClass
doing element.getElements("input[class=emailmessages]") vs element.getElements("input.emailmessages") is probably slower but may return inputs that have other classes as well that you may not want.
el.getParents() will return all parents. you then iterate them again. twice. are you sure you don't mean just .getParent(), singular? if so, or if its one parent only, you are applying a .each on a single element, which is an unnecessary hit.
if your logic needs to remain then consider this as a single iteration:
store and walk.
var parents = el.getParents();
parents.each(function(p) {
p.addClass("unchecked").removeClass("selected");
});
all in all:
$("usermail").getElements("input.emailmessages").each(function(el) {
var parents = el.getParents();
parents.each(function(p) {
p.addClass("unchecked").removeClass("selected");
});
});
of course, with Slick / mootools 1.3 you can do it a lot more simple:
on this dom:
<div id="usermailbox">
<div class="selected">
<input class="emailmessages" />
</div>
<div class="selected">
<input class="emailmessages" />
</div>
<div class="selected">
<input class="emailmessages" />
</div>
</div>
the following will return all the divs:
// gets parents via a reverse combinator. you can do !> div or whatever to be more specific
// document.getElements("#usermailbox input.emailmessages !") will return all parents...
// ... in a single statement, inclusive of body, html etc... same as getParents
var divs = document.id("usermailbox").getElements("input.emailmessages !> ");
// single iteration per parent:
divs.each(function(div) {
div.removeClass("selected").addClass("unChecked");
});
of course, you can just do $("useremail").getElements("div.selected,div.unChecked") to get to these divs at any time anyway.... so it all depends on your DOM and needs, there must be a reason why you are doing what you are doing.
bottom line for perf:
cache results of lookups into vars. if you call $("someid") twice, cache it in your scope. if you do $("someid").getElements() twice, that's more than twice as bad in performance... and add .getParents() twice, thats ... n-times as bad now...
avoid applying chaqined methods to collections like this: collection.addClass("foo").removeClass("bar") - it will iterate it twice or n-times, go for a single .each callback instead, it will be much faster.
try to avoid reverse combinators or parents lookups if possible, go direct. you can use nth-child etc - other ways to walk your DOM than to get to the input and walk back. Especially when you don't really need the input itself...
.getParents(selector) will limit the types of parents you want. .getParents() will return buckloads, all the way up the parent / anchor dom node, often including the same ones for siblings.
always create a local scope with anonymous functions so you don't pollute your global object or upper scopes - IE does not like that and makes accessing variables slow.
Hope some of this makes sense, at least :) good luck and remember, speed is only relative and is as good as your performance in the slowest browser you support.

Is binding events in jQuery very expensive, or very inexpensive?

I just wrote a $().bind('event') function and then got concerned that this kind of a call might be very expensive if jQuery has to run through each element in the DOM to bind this event.
Or maybe, it's just about as efficient as an event could be. The jQuery docs I've read aren't making this clear. Any opinions?
There are two things that can make your event binding code slow: the selector and the # of bindings. The most critical of the two is the # of bindings, but the selector could impact your initial performance.
As far as selectors go, just make sure you don't use pure class name selectors like .myclass. If you know that the class of myclass will always be in a <div> element, make your selector be div.myclass as it will help jQuery find the matching elements faster. Also, don't take advantange of jQuery letting you give it huge selector strings. Everything it can do with string selectors it can also do through functions, and this is intentional, as it is (marginally, admittedly) faster to do it this way as jQuery doesn't have to sit around to parse your string to figure out what you want. So instead of doing $('#myform input:eq(2)'); you might do $('input','#myform').eq(2);. By specifying a context, we are also not making jQuery look anywhere it doesn't have to, which is much faster. More on this here.
As far as the amount of bindings: if you have a relatively medium-sized amount of elements then you should be fine - anything up to 200, 300 potential element matches will perform fine in modern browsers. If you have more than this you might want to instead look into Event Delegation.
What is Event Delegation? Essentially, when you run code like this:
$('div.test').click(function() {
doSomething($(this));
});
jQuery is doing something like this behind the scenes (binding an event for each matched element):
$('div.test').each(function() {
this.addEventListener('click', function() {
doSomething(this);
}, false);
});
This can get inefficient if you have a large amount of elements. With event delegation, you can cut down the amount of bindings done down to one. But how? The event object has a target property that lets you know what element the event acted on. So you could then do something like this:
$(document).click(function(e) {
var $target = $(e.target);
if($target.is('div.test')) { // the element clicked on is a DIV
// with a class of test
doSomething($target);
}
});
Thankfully you don't actually have to code the above with jQuery. The live function, which is advertised as an easy way to bind events to elements that do not yet exist, is actually able to do this by using event delegation and checking at the time an action occurs if the target matches the selector you specify to it. This has the side effect, of course, of being very handy when speed is important.
The moral of the story? If you are concerned about the amount of bindings your script has just replace .bind with .live and make sure you have smart selectors.
Do note, however, that not all events are supported by .live. If you need something not supported by it, you can check out the livequery plugin, which is live on steroids.
Basically, you're not going to do any better.
All it is doing is calling attachEventListener() on each of your selected elements.
On parse time alone, this method is probably quicker than setting inlined event handlers on each element.
Generally, I would consider this to be a very inexpensive operation.

Categories