This question already has answers here:
Best practice to avoid memory or performance issues related to binding a large number of DOM objects to a click event
(4 answers)
Closed 5 years ago.
Recently I've been working on a relatively simple personal project where I need to generate quite a fair amount of Javascript to bind various event handlers to elements (such as on('click')) and it got me wondering about the efficiency of generating multiple on('click') definitions per element, based on the values in the array (which could change per page load), or having a single function that binds it to every element. For example:
PHP generating jQuery
<?php
foreach($var as $key => $val){
echo '$("' . $key . '").on("click", function(){
// do something
});';
}
// Which will generate:
// $(elemkey1).on("click", function(){ // do something });
// $(elemkey2).on("click", function(){ // do something });
// $(elemkey3).on("click", function(){ // do something });
// $(elemkey4).on("click", function(){ // do something });
// ...
Pure jQuery
$(elem).each(function(){
// do something
);
So my question is: Which would be the most efficient way of declaring something like the above?
Obviously the second example is dependant on the selector used (whether it's an id or class for example and I'm fully aware of the caveats here) but that aside, assuming the right selectors are used, I'm curious to know if there's a slight performance benefit in declaring event handlers per element explicitly using a PHP for loop as opposed to the jQuery .each() or similar method.
One selector is preferred
There is a performance difference, and once you break the 3000 or so element size it should become visible. At 10,000 it is undeniable.
It is vastly more efficient to use a single selector one time in order to handle the event as that way the click event only needs to be checked once when triggered.
Event dispatch is the primary reason
The main reason for the change in efficiency is the way events work. When an event is dispatched, it must propagate all the way down to the element through the DOM to the target, and then bubble all the way back up. This is highly inefficient compared to having the event triggered further up in the DOM.
Please read 3.1. Event dispatch and DOM event flow at the W3C for some of the finer grain details.
Here is one of their diagrams from that section:
Bandwidth and caching can also be problematic in the php-generated scenario
Aside from the JavaScript execution angle, there is also the issue of the php generated code. While it may be negligible for a small set, it is problematic from a bandwidth perspective for larger sets (this comes into play more significantly when dealing with mobile browsing). Moreover, as the server is generating the javascript code, it will be very difficult to cache for the browser assuming that the sets will often be different therefore generating different selectors. A pitfall would also be that without a cache breaking scheme for the selector sets (perhaps making use of the terms used to generate them) then the wrong set of selectors could be cached.
[Is] there a slight performance benefit in declaring event handlers per element explicitly using a PHP for loop as opposed to the jQuery .each() or similar method?
In the absence of event delegation, the echo loop in PHP code has two additional overheads:
bandwidth (the served page size is larger)
The echo loop creates a separate anonymous function client side for each element it is assigned to:
$(elemkey1).on("click", function(){ // do something });
$(elemkey2).on("click", function(){ // do something });
$(elemkey3).on("click", function(){ // do something });
$(elemkey4).on("click", function(){ // do something });
would create four separate function objects to handle clicks.
Client side assignment in JQuery only creates one handler function object, when evaluating the call parameter expression in the statement:
$(elem).each(function(){
// do something
);
If the number of elements is huge, event delegation by capturing the event on a parent node higher in the DOM, and checking which element was the target of the (click) event is preferable. From comments, this question on best practice goes into further detail.
In light of #TravisJ 's answer, it would appear that event capturing may increase client responsiveness in supported browsers. The idea is to be notified of the event during the capturing phase and stop it propagating further if processed by the click handler.
Related
TLDR Below
JS Fiddle To Demo
I've been really involved in recreating the tools that are foundations of premiere JS Libraries to better improve my skills. Currently I'm working on functional data-binding a la Angular.
The idea of data-binding is to take data and bind it to elements so that if manipulated all elements subscribed will change accordingly. I've gotten it to work but one thing I hadn't considered going into it was the issue with innerHTML vs value. Depending on the element you need to change one or the other( in the demo above you'll see that I needed to specifically single out the button element in a conditional statement because it has both, but that's kind of a fringe case )
The issue is that in order to capture a SPAN tag update I needed to trigger an event to happen, and the easiest one to manipulate for Text Boxes/Textareas was 'keyup'.
In my function then, if you pass in an element with no value property we assume you're going to be updating innerHTML, and we setup an observer to determine if the element ever mutates, and if it ever does, the observer will emit a 'keyup' event.
if (watchee.value == void(0)) {
var keyUpEvent = new Event('keyup');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
watchee.dispatchEvent(keyUpEvent);
});
});
observer.observe(watchee, {
childList: true
});
}
Now it may just be my paranoia, but it seems like I might be tunneling into a can of worms by faking 'keyup' on an element that doesn't natively have that support.
TLDR:
I'm curious if there's an alternative way to make, a.e. a span tag reactive other than faking a 'keyup'/'keydown'/'change' event? For instance, is there a way that I can make my own pure event(by pure I mean not reliant on other events) that checks if innerHTML or value has changed and then performs a function? I know that this is probably possible with a timer, but I feel like that might hinder performance.
EDIT: just an aside. In the demo the function called hookFrom works by taking a DOM node and returning a function that will take the receiving dom node and continues to return a function that will take additional receiving dom nodes. :
hookFrom(sender)(receiver);
hookFrom(sender)(receiver)(receiver2);
hookFrom(sender)(receiver)(receiver2)(receiver3)(receiver4)...(receiver999)...etc
JS Fiddle To Demo (same as above)
There is nothing inherently wrong with creating a similar event on a DOM node that doesn't natively have that functionality. In fact this happens in a lot of cases when trying to polyfill functionality for separate browsers and platforms.
The only issue with doing this sort of DOM magic is that it can cause redundancy in other events. For instance the example given in this article: https://davidwalsh.name/dont-trigger-real-event-names shows how a newly minted event using the same event name can cause problems.
The advice is useful, but negligible in this specific case. The code adds the same functionality between text boxes, divs, spans, etc... and they are all intentionally handled the same way, and if the event would bubble up to another event, it would be intentional and planned.
In short: There is a can of worms that one can tunnel into while faking already explicitly defined event names, but in this case, the code is fine!
Assuming I have this code, button with inline onclick event:
<button onclick='..js stuff..'>mybutton</button>
I have this button created multiple times because of server-side loop.
Or I would be better giving a class to these buttons, and just do (using jQuery):
$(".button-class").on('click',function(){..});
What is better in terms of performance?
My questions are-
In the inline onclick, does it creates a handler for each button?
In jQuery event binding, does the handler is created only once, and is binded for each button, or, here as well, the handler is created multiple times?
I guess that these are the factors which affect any performance difference. Perhaps the only downside for .on(..) is that I have to do DOM search by class name. (?)
The answer is: it doesn't matter.
Use the latter (jQuery binding) because it moves the code away from the DOM and makes it easier to work with.
With the inline attribute a different handler is theoretically added for each event; each attribute implicitly creates a new callback/function1 that wraps the supplied code. This handler will be replaced if the attribute (or corresponding DOM property) is assigned a different value later. In the case when all the event handlers have been created this is the "worst" approach in terms of book-keeping.
With the jQuery (addEventListener) version the same function callback is added for all the matching elements. Multiple event handlers for the same element/event may be added; care may be required to avoid unintentional repeated-binding.
Furthermore, with delegated events jQuery could avoid binding to each element separately (ie. it only binds one event handler further up the propagation chain). Depending on how many elements are to have events "attached", this could result in a significant decrease of actual events listened to while still only using a single event handler function.
The chance of their being an actual real-world performance difference between the approaches is slim-to-none, degenerate cases aside. Use the form that is most clear/extensible/maintainable which, IMOHO, is rarely the event properties; especially when embedded directly into HTML attributes. (One issue with the inline attribute form is that it cannot bind to an appropriate closure context and so it must use - ick! - global context in many cases.)
1 Browsers first only had inline events (almost exclusively specified in HTML attributes) and are well-optimized for this case. The actual event handler function is only created on demand. Consider the case of <button onclick="alert(">Hi!</button>, where the "onclick" contains a syntax error in the inline JavaScript. A modern browser will only parse the JS (and thus only create the actual handler function) when the the button is clicked or the .onclick property is read.
Using onclick is frowned upon and considered bad form, you should instead be using element.on('click') with jQuery or ng-click in Angular.js.
They both result in the same number of listeners, and basically the same performance.
The counter part using on.event is: if you reload Or rerender the objects using akax you need to instanciate the on.event again, it creates more code and thats more complicated.
I get wordy sometimes: tl;dr: read the bold text.
The motivation behind deprecating Mutation Events is well understood; their efficacy in achieving many types of tasks is questionable.
However, today, I have discovered a use for them that is highly dependent on those very same undesired properties.
I will first present the question, and then present the reasons that lead me to the question, because the question will be absurd without it.
Is it possible to use the new Mutation Observers in a way that we can have the VM stop at the instant of the change (like the DOM3 Mutation Events do), rather than report it to me after the fact?
Basically, the very thing that makes the Mutation Observer performant and "reasonable" is its asynchronicity, which means (necessarily, it seems) throwing away the stack, pushing a record mutation to a list, and delivering the list to qualified Observers at the next tick or several ticks later.
What I am after is precisely that stack trace of the DOM3 Mutation Event. I really really hope this will work, but basically the Mutation Event callback (which I am allowed to write) will have a stacktrace that will lead me back to the actual code that created my element I'm listening for. So in theory I'd write a Mutation Event handler like this:
// NOT in an onload cb
$("div#haystack").on('DOMNodeInserted', function(evt) {
if (is_needle(evt.target)) {
report(new Error().stack); // please, Chrome, tell me what code created the needle
}
});
This gives me the golden answer.
It seems that Mutation Observers will make it impossible to extract this information. What, then, am I to do once Mutation Events are completely taken out? They have been deprecated for a while now.
Now, to explain a little better the real actual circumstances, and why this matters.
I have been trying to kill a bug which I describe here: I have built a full-DOM serializer which nicely spits back out every element that exists on the webpage, and in comparing them, the broken page and the working page are identical. I have tested this and it is pretty nice. it captures every little thing that's different: Whatever hovery-thing my mouse happens to be over, the CSS class that gets consequently set will be reflected in the HTML dump. Any text of any form on the page will show up if you search it (provided it doesn't span across elements). All inline JS (and more importantly, all differences between inline JS) is present.
I have then gone on to verify that the broken page is missing several event handlers. So none of the clickable items respond to hover or clicks, and therefore no useful work can be done on the interactive form. This is not known to be the only problem, but it does fully explain the behavior. Given that the DOM has no differences in inline JS that explains the difference in behavior, then it must be the case that either the content of the linked resources or the invisible properties of elements (event handlers being in this category) are causing the difference in behavior.
Now I know which elements are supposed to have handlers, but I know not where in the comically large code base (ballpark: 200K lines of JS all loaded as one resource, assembled by several M lines of Perl serverside code) lies the code that assigns the events.
I have tried JS methods to watch modifications of object properties, such as this one (there are many, but all work on the same principle of setting setters and getters), which works the first time, and then subsequently breaks the app afterward. Apparently assigning setters and getters cause the system to stop functioning. It's not clear to me how I can take that approach of watching property assignments to a point where i can get a list of code points that hit a specific element. It might be feasible, but surely not if I can only fire it once, and it breaks everything thereafter.
So watching variables with JS is out.
I might be able to manually instrument jQuery itself, so that when my is_needle() succeeds on the element processed by jQuery, I log all event-related functions performed by jQuery on that element. This is dreadful, and I will resort to this if my Mutation Observer approach fails.
There are yet more ways to skin the cat of course. I could use the handy getEventListeners() on my target element when it is working to get the list of event listener functions that are on it, and then look at the code there, and search the code base to find those functions, and then analyze the code to find out all the places there those functions are inserted into event handlers. That is actually pretty straightforward.
Now I know which elements are supposed to have handlers, but I know not where in the comically large code base (ballpark: 200K lines of JS all loaded as one resource, assembled by several M lines of Perl serverside code) lies the code that assigns the events.
Have you considered simply instrumenting .addEventListener function calls one way or another, e.g. via debugger breakpoints or by modifying the DOM element prototype to replace it with a wrapper method? This would be browser-specific but should be sufficient for your debugging needs.
You also might want to try firefox's tracer, available in nightlies I think. It basically records function execution without the need to use breakpoints or instrumenting code.
Consider the following code:
$('div').click(function(){
$(this).animate({height:100}, 500)
$(this).css({opacity:1});
});
Versus:
$('div').click(function(){
$(this).animate({height:100}, 500);
})
.click(function(){
$(this).css({opacity:1});
});
Does jQuery or JavaScript essentially "compile" the second code sample into something like the first rather than maintaining two separate event handlers? I ask about whether jQuery or JavaScript does this because I'd also be interested to know if such "compilation" is a feature of native JS or something implemented by jQuery.
It seems to me that this "compilation" is not actually done, at least not in a way that eliminates the differences between the two code samples. Using JSPerf, I compared the speed between each one and it appears that the first code sample is substantially faster.
Handlers are fired in the order they are bound and each $('div').click() binds another handler to the element in question. In your case the first one only binds 1 event handler and thus performs faster because it only fires one event. The second binds two event handlers, and thus is slower because it fires two events instead of one (more overhead).
I think they are maintained as two separate events. When triggered, they get executed in the same order they were bound.
I have a grid and there is a column which contains <a> anchor tag with some additional information in <data-..> tag and has a class name <class='myspeciallink'>. And in my unobtrusive JS script I select all the elements with that class name and apply live('click'). I need that to be live() because the grid gets generated in the runtime.
What happens inside the live('click') handler? I use that additional data and add a <div> to the page based on that data. Which in its turn used to generate jQuery UI dialog. It works great on my computer.
But! How could that work in real-world? Should I be bothered about possible performance implications? I feel that applying live() on more than a dozen elements instantaneously
would affect the performance. Especially with rather complicated handler like mine - it needs to get the data, parse the data, create a div, apply a dialog and etc.
Does that smell like a bad design? Could you suggest a different approach or my concerns are unfounded? Can I use some sort of a profiler tool to find the bottlenecks in my javascript?
UPD: Still nobody suggested any profiling tool. firebug and chrome dev tools are good, but maybe there is something even better?
live("click") is actually better up-front from a performance standpoint: Instead of binding an event handler to each matched element, you're applying a single event handler which waits for events to bubble up and then sees if the element that triggered the event matches the selector .live was called on.
Compare this to $('selector').click(...) which does loop over each element and bind a new event handler. live('click') has no additional overhead regardless of how many page elements match its selector. Depending on how many elements your selector matches, using .live can avoid a delay of up to a few seconds during the initial load of each page.
However, the event handler must check each event which bubbles up against its selector, to see if there is a match. This is going to add a small amount of overhead to every click event, but chances are very good that your users will not notice the difference.
Peter bailey also has a nice post about this: Performance difference between jQuery's .live('click', fn) and .click(fn)