DOM handleEvent: a cross-platform standard since year 2000
I’ve recently read long twitter threads about how to bind components methods, how to use arrow function per each method at class definition, how to add events to components and be sure the context is the expected one, and yet nobody mentioned the glorious EventListener interface and its handleEvent(evt)
method in any of these discussions …
A solution to many problems
This is a mandatory TL;DR for all developers believing they fully understood what is handleEvent
about and what does it solve:
- it’s the best solution in terms of RAM. Applications will not “explode” or crash on emerging markets devices. You won’t ever retain extra unnecessary bytes for the sake of a component context.
- one single entry point to handle every ordinary or custom DOM event. You’ll never need to bind another property, you’ll never need to change your class definition with another arrow. Your components will always be ready to handle a new listener without any maintenance at all.
- it’s more secure than reassigned properties. Whenever you
comp.method = comp.method.bind(comp)
or youcomp.method = e => comp.stuff()
you are creating a public enumerable property at runtime instead of keeping behaviors at class, object, or component definition time. WithhandleEvent
you can create a single private delegate to handle internally everything you want, giving you the ability to expose and use the same component both internally and externally. - it is always possible to remove a listener. You’ll always be able to drop that listener that’s not supposed to happen again. No listeners defined in closures issues, no unreachable bound methods as in
el.addEventListener(type, this.method.bind(this))
which makes you lose control of your own application. - listeners have a meaningful context instead of a redundant one. Every listener function will be invoked with a context that is exactly the
event.currentTarget
information any event would already carry. - strawberry on top: if you add by accident 2 times the same object as listener, it won’t be fired/dispatched/triggered twice ’cause it was already known, so the platform will avoid redundant dispatches 🎉
We’re so obsessed with the context and yet use since year 2000 the only pattern that makes the context ambiguous? Let’s stop this madness!
As summary, every time you need a context
and you think handleEvent
is not the solution, think again, because it’s most likely superior to any alternative you are using now.
How does it work?
The handleEvent
ABC is that any object that implements such method will be invoked as obj.handleEvent(event)
once the event happens.
The method can be inherited obtaining exact same results, meaning there is one single method in memory instead of O(N).
Which in turn it means we can use classes too.
Got it? You already know pretty much everything you need to know about handleEvent
but I’d like to walk you through all the points I’ve previously made in the TL;DR list.
About Memory Consumption
You don’t need a degree in math to realize if you attach one or more new properties to every new instance you create, the used memory will be inevitably more. Of course, bind
usage might be lighter than arrow function
, yet if you don’t need to use either at all, why would you?
On top of that, if you try to take a memory snapshot of this basic benchmark you will realize that having a shared handleEvent
costs basically zero while preserving real-world performance integrity.
Look again, check at the ArrowHandler
and sum up the BoundHandler
together with the Bounder
and the bind
one.
Now double check Static
and DynamicHandler
numbers.
Now ask again yourself: do you need to use extra memory for a context when you have a native behavior that does it for you?
One instance to rule them all
An object that implements handleEvent
can be used both internally and externally to intercept and react to any sort of listener.
Using prototypal or classical inheritance to add a minimal amount of abstraction is straight forward, and the result is quite stunning.
But Security First
Some developer mentioned to me components created in this way give other parts of the code the ability to arbitrarily remove any listener they use.
I am not sure what’s the point to bring this up, since attaching arrows or bound methods to the instance suffer exactly the same issue with on top the unnecessary memory bloat, but it’s deadly simple to delegate through a single extra reference everything I’ve shown, and talked about, so far.
As result we will have only one single extra reference instead of O(N) bound methods or O(N) arrow functions: we still win in terms of memory consumption and performance will still be excellent for any real-world application.
On top of all this, the Component could be used externally and be monitored internally, as example simply sending a delegated
reference to the listener and react differently if that’s the case.
As you can see, there are many ways to go wild about abstracting on top of handleEvent
so give it a try and see what you come up with!
Listeners can always be removed
I’m restating and repeating myself here, and I’ve written this in this October 2015 blog post too, every time you add a listener and you don’t reference it it’s like creating a setInterval
without having a way to ever stop it.
This point is not even strictly coupled with handleEvent
, I mean … you always want to be in control of your listeners and eventually drop them when/if needed, right?
So here the catch: handleEvent
simplifies this extremely well and at the end of the day, only if you really need to never drop a listener, you can always chose the runtime bound callback or the one-off arrow function as listener.
If you need a context, do it right
I think it’s clear by now why handleEvent
is the right choice when you use classes or components.
If you accidentally add an object twice, you’re safe
If nothing else convinced you by now, think about the following:
node.addEventListener(type, obj.method.bind(obj));
node.addEventListener(type, obj.method.bind(obj));
Any guess about how many times the obj.method
with context obj
will fire once the event type
happens? Twice!
Do you have any valid use case for that? NO!
But if you add the same instance as listener, any guess how many times that will be triggered? Once!
node.addEventListener(type, obj);
node.addEventListener(type, obj);
I could repeat that forever, yet the type
handler will trigger just once.
What about Frameworks?
For what it matters, every code of mine since about year 2000 supports handleEvent
behavior because I know, and use, this pattern since long time so hyperHTML, as example, is safe to use without any issue.
React will hopefully listen to me 😁 and implement something user friendly like this proposal.
About other frameworks? I don’t know.
I would expect that people writing frameworks read, know, and understand standards as much as I do.
The reality is that I keep reading about developers unaware of this long-time available pattern, or incapable of understanding its potentials.
But hey, do yourself, your team, and your product, a favor:
- is your framework supporting
handleEvent
? Awesome! Start seeing where this pattern can be useful. - is it not? Please file a bug or send a PR to such framework asking why they don’t and, maybe, point at this blog post if they need to know more.
As Summary
It’s our duty to also care about cheaper devices with less RAM constrains: the Web should be available for their owners too.
Even complex Progressive Web Applications, if done right, and as you can see, don’t necessarily need to bloat CPU and RAM.
handleEvent
brings better memory usage on top of many possible handy patterns that can only simplify our life as Web developers.
Maybe 2017 will be the year this interface will find its justice? I hope so!