Are You Listening to This?

I am in the process of learning JavaScript and a bit of jQuery through Launch School. Of course, you can’t get very far in JavaScript before encountering callbacks and event listeners. And, if you’ve ever been a JavaScript beginner you’ve probably also spent some time struggling with function execution context and the value of this. After reading an article about event listeners I became interested in how these two go together and decided to dive in.

As a side note, you should already be comfortable with both attaching event listeners, the this keyword, and function execution context. I won’t spend time explaining these concepts on their own.

First, let’s set up some simple HTML that we’ll be using throughout:

Basically, we have an unordered list containing three list items, each containing a link. Then, below we have some <p> elements where we will be outputting the results of our experiment. The whole thing is wrapped in a <div> which will make it easier to target the correct output elements when we have several of these side by side.

The Event Handler

Let’s set up the function we’ll be using as an event handler throughout this demo:

Here we select the surrounding <div> and then use querySelector to target the appropriate <p> elements to output the tag names of event.target and event.currentTarget, as well as this. We also output the actual text we clicked on, just to show where we clicked.

So, let’s make our page interactive and attach this function to an element, listening for a click event. We have a few different ways of doing this. First we’ll try vanilla JS, then we’ll explore jQuery.

Attach To All Children

A naive way of doing this might be to add an event listener to each <li> or <a> element in the list. Let’s do that here:

Of course, this is inefficient if you have to attach listeners to a large number of items. It also has the disadvantage that if we would like to dynamically create more <li> elements later, we’ll have to manually attach listeners to each of those. (Note that for our purposes, we also could have used addEventListener instead of attaching a method to the onclick property. The behavior for this is the same.)

Attach To Parent

Instead, we can take advantage of event propagation and let the parent <ul> element handle any click event on one of its children:

Note, that if you have any children in <ul> that is NOT a <li> element (or even some padding) those children or the <ul> itself will now respond to clicks. If that is not your intended behavior, add a check in the clickHandler function itself to ignore events where the target is not the correct one.

Beware the Arrow!

One thing to note here is that using an ES6 arrow function can be problematic if you want to rely on the value of this. Since arrow functions do not have their own binding to this, it will use the value of thiswhere the function is defined (usually window or document depending on whether you are defining it in the global scope or inside of an event listener like a DOMContentLoaded listener).

Let’s see these all in an example. I’ve taken the examples from above, tweaked them to be used together on the same page and added another using an arrow function. When you click on a list item it should update the values below. Try predicting what each one will show before you click and see if you can explain why:

CodePen example of JavaScript Event Listeners

Note how the value of this is usually the same as currentTarget. However, when using an arrow function, currentTarget and this are different since the arrow function’s execution context is window.

Attach To Children With jQuery

With jQuery we can use the on() method to attach a listener. If you are unfamiliar, check out the documentation. There are a few different ways of calling this method and we won’t cover them all.

First, let’s attach a listener to each <li>element individually. We can do this by selecting all the <li>elements within a <ul> like this: $('ul li'). We then call on() on the object and pass the event type and a callback function:

Attach To Parent With jQuery

This will go through the list of <li>elements and attach a click event listener to each one. This means that this is set to each <li>element. But this technique suffers from the same inefficiency as in vanilla JS, as well as the need to attach listeners to any new elements as we add them.

So, we could attach the listener instead to the <ul> element:

This is equivalent to our second vanilla JS example above. It solves the inefficiency problems and allows us to add child elements and not have to worry about attaching listeners each time we add a new child.

The Best of Both Worlds

But, in jQuery we can do one better. The on() method allows us to pass a selector argument. The docs say this is:

A selector string to filter the descendants of the selected elements that trigger the event.

Here’s the syntax:

So, we can still attach just one event listener to the parent <ul>, but this will only respond to events that have come from elements specified by the selector. Note that this does not mean that the `target` has to be that element. In our example, the `target` of our click is usually the <a> child of each <li>. However, the handler we have attached to the <ul> element responds because it bubbled up through a <li>element.

The additional benefit we get here is that both event.currentTarget as well as this is set to the element specified by the selector and not the element where the listener is attached. In a way, we get the best of both worlds: we are able to use just one listener attached to the parent but the handler executes in the context of the child element as if we had added a listener to the child.

Play around with these examples here:

Note: in jQuery the same cautions apply when using arrow functions

Wrapping Up

Let’s quickly review some takeaways:

  1. In general, the execution context for an event handler will be the element to which the handler is added. This is usually going to be the same as the event.currentTarget property.
  2. If you use an arrow function as the event handler, your execution context will not be the element and will most likely be either window or document instead.
  3. We can use the jQuery method on() to attach a single event handler to a parent element but use the selector argument to filter events based on whether the event has bubbled from or through an element that matches the selector string. This has the benefit of setting both this and currentTarget as if the handler were attached to the child element, while maintaining the efficiency and flexibility of delegating event handling to the parent.

I began this deep dive after encountering some confusion in this area and I wrote this article as a way to clarify things for myself. I’m glad I now better understand the execution context of event handlers and I hope you have learned something as well.

In my own code I think I will be referencing DOM elements using event.target or event.currentTarget and leaving this for cases where I want to explicitly bind another object to an event listener. Since the event object will always be passed to the handler, it seems more explicit and less error-prone.

Further Exploration

If you’re intrigued by this and want to have some more fun, try to implement a jQuery “selector style” event listener in plain JavaScript. I have successfully implemented most of the functionality, including the correct this within the callback. The tricky part is to get event.currentTarget to point to the selected child instead of the parent element where the event is attached. I haven’t spent the time to research that yet.

Please comment below with your biggest takeaways and any other questions, comments, or corrections!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Keller

Josh Keller

Father of two. Musician. Music therapist. Teacher. Aspiring software engineer.