JS – Avoid duplicating addEventListener()

I see this mistake way more often than I should. Someone adds an event listener with an anonymous function callback to an element (like a button) then puts it in a a function that may get called multiple times. While this very common setup may seem harmless, it can create some serious issues.

The problem is this setup will add multiple listeners to the element, a new one each time the function is called, meaning three calls will add three listeners, then when the event is triggered the callback will be run three times. This JSFiddle demo clearly shows the scenario in action.

But wait, read what MDN tells us about addEventListener():

If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded. They do not cause the EventListener to be called twice, and they do not need to be removed manually with the removeEventListener method.

In that case, why is the listener and callback being applied and run three times?

The answer lies in how we are creating the listener. Our callback is created using an anonymous function, a common pattern JS devs use when they need to run code that is only needed when an event is triggered. The problem is an anonymous  function is not considered a “same parameter”. Let me explain.

When your code is run, the JavaScript engine “reads” it twice: the first time it finds and registers all named functions (function myFunction() { ... }), then it executes your code from top-to-bottom. All named functions are given a special place in memory which is referred back to when the function is called (this is not an accurate explanation of the technical process, but it will suffice). Anonymous functions are not considered a named function, thus every time it is executed a new copy is placed in memory, and since the function is not named, it cannot be referenced again and is garbage-collected when execution is completed.

Armed with this new knowledge, looking back at our example we can understand how the anoymous function used in our callback is applied each time clickButton() is called. The question now is how we do we refactor our code to stop attending Functions Anonymous prevent applying multiple listeners to our button and only add it once?

There are three ways we can fix our code. First, we can move our event listener into the global namespace and do away with clickButton() entirely, as this demo shows. However, this method is not always possible in a code base. A second way might be to move the listener into the global namespace and pass clickButton() as the callback parameter as seen here. The biggest problem with these ways is they assume the element to listen to is always available, which would not be the case with dynamically injected and/or removed content or events that are only available once a certain function is called. In that scenario, a third way to refactor is required. The gist of the refactor is to move the callback’s code into a named function nested in the main function, pass the function’s name to removeEventListener() as the last statement, then pass the new function into addEventListener(). In this refactor, the listener is removed once it is triggered, allowing it to be applied again later at the appropriate time (if needed).

If it looks like the third refactor is acting the same way as the initial implementation… well, it is, but only on JSFiddle. I am not sure why it is happening, as I used that exact method very recently to address multiple listeners being applied and it worked correctly (that commit is actually what prompted me to write this post). I do not know why it is not working as it should on JSFiddle. Sorry about that. 😦

Congratulations! You now know how to avoid adding duplicate event listeners to a single element! 😀 Now go out into the world and write better JavaScript!

-Caleb

Advertisements

3 thoughts on “JS – Avoid duplicating addEventListener()

  1. I’m fairly sure the third option does not help much, as the inner function is still re-created on each call to clickButton()

    My solution to this problem tends to be to bind the handler method to a property of the attacher function (eg. clickButton) or its prototype. This provides a single function identity tied to clickButton while still avoiding duplication.

    Example: http://jsfiddle.net/Tribex/atL1tzf9/

  2. You can avoid this by attaching the event to the body instead and just checking the event.target is the element that your looking for and calling a specific method from that condition. If using jQuery it would be something like $(‘body’).on(‘click’, ‘.myElm’, function() { // do something });

Triangular Reactions

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s