Are You Listening to This?
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.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
<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
document depending on whether you are defining it in the global scope or inside of an event listener like a
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:
Note how the value of
this is usually the same as
currentTarget. However, when using an arrow function,
this are different since the arrow function’s execution context is
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
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:
selectorstring 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
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
Let’s quickly review some takeaways:
- 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
- If you use an arrow function as the event handler, your execution context will not be the element and will most likely be either
- We can use the jQuery method
on()to attach a single event handler to a parent element but use the
selectorargument 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
currentTargetas 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.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.
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!