Why it is bad practice to register events to document in jQuery? - javascript

Whenever I read about event registration in jQuery they all say that we should try to add event handlers to the nearest parent, and avoid adding event listeners to the document, because according to resources they are slow and not efficient.
But why they are slow? Apparently it's because the event will have to bubble up to the document, which will take time. Then it will be compared to a list of selectors to call, e.g.
$(document).on("click", ".abc", function(){ })
$(document).on("click", ".abc2", function(){ })
So here if I click an element, the click event will be bubble up to the document and then it will match the selector list (".abc, .abc2")...and that is inefficient. OK I got it but what if I have only have one selector in the list ? e.g.
$(document).on("click", "*", function(){ })
Will it be slow too ? If so why?
Basically i'm trying to create a google's jsaction similer lib, so I will write like this:-
$(document).on("click", "[jsaction]", function(){ })
Because this will be the only selector, so i don't think it will be slow ? or will it be ?
and if attaching event to document is not efficient, then what about a completely ajax application? my application is completely ajax, and every page will be downloaded by ajax. Is there another, more efficient solution?

Event delegation basically uses 2 different process,
The event bubbling, For example, when you click over an element that
event will be bubbled up to the document to fire the relevant event.
The match, after reaching the document(during event bubbling) the
fellow who caused the event bubbling will be verified with the
selectors attached with document. If it matches then the relevant
event will be fired.
So you are advocating about the match by stating a generic selector. To be frank, matching 2 or 3 elements will take less time than traversing up to the document during event bubbling. At that case if you use a static closest parent instead of document, the traversing time will be reduced and that will hike the performance.
Though match takes less time, when it comes with 15+ elements for matching, that will also affect the performance even when you use closest parent instead of document.
So the summary is, we have to use event delegation sparingly with common sense by knowing the above two different process takes place under the hood.

Related

JQuery trigger event performance on document

I'm building a publish/subscribe framework in jQuery and I'm wondering if there are performance penalties to triggering and listening to events on the document root?
All articles I can find describe the penalty you obviously get for listening to events on the document level, but triggering them from a more specific element - listening for a "click" for example.
My pseudo code:
$(document).on("myCustomEvent", function() {
alert("Event triggered");
});
$(document).trigger("myCustomEvent");
My event does not have a fitting home in the DOM but I could always add a dummy element to trigger from/listen to if it's better, but would rather not. What do you think?
trigger() propagates up the DOM. It also interacts with all elements matching the object, which can cause conflicts / unexpected behavior. Neither of these are inherent* performance hits -- and since you're using a custom event, you shouldn't have an issue with other elements matching the object. That's really only a problem when trigger() is used with a real event, like trigger('click').
If you're concerned about either of these (bubbling up the DOM or other matching elements), you can use triggerHandler()
This post goes into more detail about the differences between trigger() and triggerHandler()

Are there performance drawbacks to using a delegated event handler in JavaScript and jQuery?

I am using a delegated event handler (jQuery) in my JavaScript code so stuff happens when dynamically added buttons are clicked.
I'm wondering are there performance drawbacks to this?
// Delegated event handler
$(document).on('click', '#dynamicallyAddedButton', function(){
console.log("Hello");
});
How would it compare to this, performance-wise?
// Regular event handler
$("#regularButton").on('click', function(){
console.log("Hello Again");
});
Looking at the jQuery documentation, it seems that events always bubble all the way up the DOM tree. Does that mean that the further nested an element is, the longer an event will take to work?
Edit: Is there a performance benefit to using JavaScript's event delegation rather than jQuery's? is asking a similar question, and the answer there is useful. I am wondering what the difference is between using a regular event handler and a delegated event handler. The linked questions make it seem like events are constantly bubbling up the DOM tree. With a delegated event handler does the event bubble up to the top and then back down to the specified element?
Every time you click pretty much anywhere in the document, the event is going to be manually bubbled up to the document element (after the natural bubbling takes place) and will run the selector engine for every element between the clicked element and the document.
So if you click on an element nested 20 elements deep, the selector engine will run 20 times for that one click.
Furthermore, this will happen for every selector the document has. So if you give it 20 selectors and click 20 elements deep, the selector engine has to run 400 times for that one click. (The manual bubbling happens only once, of course.)
Selector-based delegation is fine, but try to keep it closer to the targeted element(s) if possible.

Event delegation on mouseover

When creating click events, I do my best to bind them only once – generally to a parent shared by all the nodes expected to trigger the event. I'm curious, however, what the best practice is with mouseover events: Does it still make sense to bind an event to a parent when the result would be the constant firing of the event on mouseover? What's the most efficient practice?
In order to provide some closure to this question, I'm going to paraphrase/quote some relevant notes from this answer: 'Should all jquery events be bound to $(document)?', which was referenced above by #Faust:
Event delegation does not always make your code faster. Unless you're binding to dynamic elements or a ton of elements, you should bind event handlers directly to the objects where the event happens as this will generally be more efficient.
More specifically, here are times when event delegation is required or advantageous:
When the objects you are capturing events on are dynamically created/removed and you still want to capture events on them without having to explicitly rebind event handlers every time you create a new one.
When you have lots of objects that all want the exact same event handler (where lots is at least hundreds). In this case, it may be more efficient at setup time to bind one delegated event handler rather than hundreds or more direct event handlers. Note, delegated event handling is always less efficient at run-time than direct event handlers.
When you're trying to capture (at a higher level in your document) events that occur on any element in the document.
When your design is explicitly using event bubbling and stopPropagation() to solve some problem or feature in your page.
Original answer by #jfriend00
So, I know this question is long dead, but I figured I might as well answer with a way to do this.
With dynamic-elements, you can establish a mousemove listener on the parent div/container, and then query within the div for elements with a :hover attribute.
For example:
<div class="list-container">
<ul class="dynamic-list-content">
<!-- actual list elements provided by js -->
</ul>
</div>
Then:
var listContainer = document.querySelector('.list-container');
listContainer.addEventListener('mousemove', function(e) {
var hovered = listContainer.querySelector('li:hover');
// do something with the hovered element here.
});
Note that (as you mentioned) this will fire a lot, but no more than if you added a mousemove event listener to the individual entries. And you could debounce this a bit, using data-attributes, unique ids, etc. From my tests though, it's pretty performant in Chrome.
you can also stop the propagation of events. More info here: http://api.jquery.com/event.stoppropagation/ and here event.preventDefault() vs. return false

watch for dom element creation?

So, basically I have a plugin that I want called on an element if it is created in the dom,
E.g. let's say I have a plugin called domWatch that runs the function if the specified selector gets created inside the selector it was called on.
so:
$('#container').domWatch('.mySelector', function(element){
$(element).myPlugin();
});
$('#container').append($('<div />', {class: 'mySelector'})); //the new element should now have the myPlugin plugin called on it.
Is this possible to do?
You can use the arrive.js library that I developed for the exact same purpose (uses Mutation Observers internally). Usage:
$('#container').arrive('.mySelector', function(){
$(this).myPlugin();
});
Have a look at this clever hack: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/. It uses CSS3 animations to detect when an element was inserted into the DOM.
The problem with 'listening for when a DOM element is created' is the performance hit, so maybe you should consider a different approach to solve your problem.
I'm sure there are many, way simpler ways to achieve your goal.
If you are targeting very modern browsers only you could check out MutationObserver, which is designed for that objective.
https://developer.mozilla.org/en-US/docs/DOM/MutationObserver
Sounds a lot like Live Query. Although this does involve continuous polling which does have a performance hit.
Here are some demos.
It looks like a job for the delegated form of the .on() event:
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the
delegated event handler is attached, you can use delegated events to
avoid the need to frequently attach and remove event handlers. This
element could be the container element of a view in a
Model-View-Controller design, for example, or document if the event
handler wants to monitor all bubbling events in the document. The
document element is available in the head of the document before
loading any other HTML, so it is safe to attach events there without
waiting for the document to be ready.

When using jQuery on(), why use (document) vs. the element itself?

I'd like a jQuery expert in their own words to explain why the $(document) identifier is recommended by others for jQuery's on() statement vs just using an element itself
Example 1: Why is using $(document) here better then Example #2?
$(document).on("click", ".areaCodeList a", function(){
// do stuff
});
Example 2: Why is using the element this way considering not good practice compared to Example 1?
$(".areaCodeList a").on("click", function(){
// do stuff
});
Both of those are valid.
The former works for dynamically added elements. You use document because you're delegating events on children of the document object, so events bubble up to the document level. It's also more convenient to select the closest parent you can (and the parent must exist on the page at load).
The latter still works, and is a preferred way to simply bind events to specific elements.
I personally don't recommend delegating through the document object, but rather the closest parent that exists on page load.
Here are the docs for on().
This is not true.
Those two lines do totally two different things.
The first one is a delegate event with the selector of ".areaCodeList a" while the second line is an event attached to the ".areaCodeList a" elements.
A delegate event will fire to every ".areaCodeList a" element although it was in the DOM when that line executed.
Anyway, attaching delegate events to the document isn't recommended at all. as written in the live docs:
Since all .live() events are attached at the document element, events take the longest and slowest possible path before they are handled
Please read the on docs:
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on(). To ensure the elements are present and can be selected, perform event binding inside a document ready handler for elements that are in the HTML markup on the page. If new HTML is being injected into the page, select the elements and attach event handlers after the new HTML is placed into the page. Or, use delegated events to attach an event handler, as described next.
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time. By
picking an element that is guaranteed to be present at the time the
delegated event handler is attached, you can use delegated events to
avoid the need to frequently attach and remove event handlers. This
element could be the container element of a view in a
Model-View-Controller design, for example, or document if the event
handler wants to monitor all bubbling events in the document. The
document element is available in the head of the document before
loading any other HTML, so it is safe to attach events there without
waiting for the document to be ready.
...
...
I think you are confusing a few concepts. It is not recommended to bind to the document element, however there are times when you want to do so e.g when binding events to dynamically added elements.
All this may seem unclear, so here is the first example which uses the class selector directly and binds a click event, the element is inserted dynamically later after the event is bound. As you can see the event never gets fired because we selected an element that was not present in the DOM at the time the event was bound. This is equivalent to a .click
Now look at this second example. Here you see we are defining the root element as the document. Which means that the click event will bubble all the way up the DOM tree and then fire if the element that was clicked has a class dynamic. This is equivalent to .live
Now, if in example one, at the time of binding the event the element was present in the DOM, the code would work just fine, as you can see here.
That being said. Here's an except from the docs which clarifies the behavior above.
Event handlers are bound only to the currently selected elements; they
must exist on the page at the time your code makes the call to .on()
So, in conclusion. Use the document element when you are sure that there's no parent element for the element you are selecting that's guaranteed to be in the DOM at the time th event is bound. If there is a parent element that is present then use that instead of the document element. This way the event will not have to bubble all the way up the document, it needs to travel only a short distance.
There's nothing "recommended" about this. The first snippet sets up a "delegated" event, the latter is a "direct" one. The documentation for on() explains these in depth.
Delegated events are necessary when you need to listen to events for elements that don't exist yet - ones that will be created dynamically after, for example, an AJAX call. They can also sometimes be better performance-wise - you need less memory to attach a "real" event handler to the document object, than to 1000 buttons.
I'd say it's still preferrable to use direct event handlers when you can, or attach delegate events to an element as close to the real event sources as you can. Having all event handlers on the document object is probably terrible for performance - you have to match every event fired against all the selectors. It's also probably needed if you need to stop an event from bubbling - if all events are caught on the document, they've already bubbled as far as they'll go.
Actually the best solution in a case such as this is not using the $(document) neither the specific element like $("selector").
The best approach is using the container of the element and bind the element selector in the on function. By doing this you avoid unnecessary event bubbling to the document.
So the code should look like:
$("this_is_the_container").on('event','my_element_selector',function(){
// do some stuff here
})
$(*selector*).on(*event*, function(){})
will apply only for elements which is already load in page at the moment of script run. If in future, will appear new elements, the event handler will be not work.
$(document).on(*event*, *selector*, function(){}
will execute event handler even the elements with same selector will appear on page after script run.
So, if you have some elements, which can appear after in random time, use
$(document).on()
else use
$(*selector*).on();

Categories