DOM handleEvent: a cross-platform standard since year 2000

Andrea Giammarchi
6 min readJun 20, 2017

--

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 you comp.method = e => comp.stuff() you are creating a public enumerable property at runtime instead of keeping behaviors at class, object, or component definition time. With handleEvent 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.methodwith 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!

--

--

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.