New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event delegation (e.g. for click listeners) #1098
Comments
Thanks so much for looking into this, it's very interesting. I suppose we would also need to consider the cost of doing this sort of thing inside the handler... let node = event.target;
while (node && !node.matches(selector)) node = node.parentNode; ..., which may be non-trivial. (I suppose we probably wouldn't be using There was a proposal the other day to add event 'modifiers' to automatically stop propagation, prevent defaults etc. Maybe if we decided we wanted delegation but weren't sure about doing it automatically, we could do something crazy like this? <ul on:click:delegate('li button')='doThing()'> (That's probably an extremely bad idea.) |
Yeah, I agree that the cost when the click handler actually runs (i.e. point # 3 above) is an important dimension to capture. Maybe in some cases the setup cost reductions aren't worth the responsiveness costs (so making it explicit rather than automatic makes sense to me). BTW I figured out the source of the Chrome discrepancy: Chrome was allocating some memory temporarily and then freeing it after a few seconds, so I had to increase the wait time in order to let the memory reach a steady state. To give you an idea of what this looks like in Windows Performance Analyzer: The initial bump is from creating the In any case, my new numbers for the 10000 elements scenario are:
That makes a lot more sense; 1 listener should be cheaper than 10000. 😅 |
Rich-Harris's point is very important when it comes to event delegation at the library level if you intend to implement the same bubbling semantics that browsers do. Another question to ask is if the setup involved to correctly reference the event handler with the related data/context when assigning all the events, i.e does Where the bench might setup 1 Factoring these two points into the bench should theoretically scale the amount of work to be done to weight on the side of implementing this in JavaScript when you consider that browsers are likely at a better position to do these two tasks more efficiently, and in either scenario both should be expected to use linear time and space. |
That's a fair point; I'm not sure exactly how much bookkeeping is required given Svelte's internal architecture. That too could impact the setup costs. In any case, this isn't a hugely important feature – users can always work around it by implementing their own delegation or using something like a virtual list. |
Just to loop back on this: I solved this using a custom implementation, and I'm no longer sure it should be handled by the framework. I had to do some custom stuff for a11y, e.g. to handle both Dead-simple example here: https://gist.github.com/nolanlawson/621081285dbbae5be97a2bdf7a6d7ce5 |
Fixed benchmark: http://nin-jin.github.io/deleg/ |
@nin-jin Do you still have the benchmark code? The results don't match this: krausest/js-framework-benchmark#561 (comment) |
I no longer remember what the problem was with the original benchmark. This code is currently unavailable. But I think a typical mistake was made there: not all browser operation was measured, but only the synchronous call of some apis. This gives false confidence about the importance of optimization. It's like comparing icebergs by the size of their small surface area. |
@nin-jin I think the benchmark link 404s because |
Yes, as I said: compared to the cost of creating a DOM, the cost of adding handlers is not noticeable. |
I brought up in the Gitter today that it might be nice to have built-in support for event delegation. For instance, if I have a list of 1000 buttons, I don't necessarily want to add 1000 click listeners to each one – I can just add one to the parent and let the event bubble up. Presumably this has a perf benefit.
But of course... all perf benefits should be investigated and measured. So I whipped up a quick benchmark to test this out.
This benchmark creates a list of n buttons and then measures the time to add the necessary event listeners, with and without delegation. It also allows you to delay the creation of event listeners to better suss out memory usage, and it prints out the response time using
performance.now() - event.timeStamp
(note: not usable in IE/Safari due to lacking high-precision timers forevent.timeStamp
). Also note that it only ever creates one function, so it's not measuring the cost of creating multiple functions.There are 3 main perf aspects we're interested in:
1. Time to set up the event listeners themselves
For this, I tested on an i7 ThinkPad in all browsers except Safari, which I tested on an i5 MacBook Air. Times reported are in milliseconds, median of 5 runs. Note that Safari throttles high-precision timings to 1ms.
So clearly event delegation wins in all browsers pretty handily. This makes sense, because the browser is simply doing less work (i.e. only calling
addEventListener()
once as opposed to multiple times). The cost of non-delegation increases as the number of child nodes increases.2. Memory usage
For analyzing memory, I used Windows Performance Analyzer with the "VirtualAlloc Commit LifeTimes" view, summing the results for each browser process. (Too much detail required to explain all the steps involved, but maybe it's worth a blog post. 😉) I ran the test with 1 run only and 10000 elements, and a delay of 10000ms to better isolate the memory costs.
Here, the results were a bit… odd. Edge and Firefox appear to use much less memory in the delegation scenario versus the non-delegation scenario, which makes sense – there's 1 listener instead of 10000. For Chrome, though, it seems to actually use more memory when you delegate, which surprised me. Results:
Chrome: 44.34MB with delegation, 32.043MB without (27.74% regression)error: amended belowI was really surprised by what I saw with Chrome, so I ran the test again and got a similar result. This may need more investigation. I also haven't figured out how to analyze memory in Safari.
3. Time it takes for the event to bubble
For this one, I took a cursory glance and observed that the response times seem to be roughly the same with and without event delegation. Maybe this is worth testing on a slower mobile device, or maybe it's worth testing with a very deep hierarchy, but I haven't gone that far yet.
Conclusion
So basically there is more work to do – I need to figure out if the Chrome memory behavior is genuine, and this probably needs to be tested on mobile devices to ensure bubbling doesn't have an exorbitantly high cost, but just based on these preliminary results it seems it's still useful to do event delegation, at least for click listeners.
The text was updated successfully, but these errors were encountered: