Quiz

Explain the concept of `this` binding in event handlers

Topics
ClosureWeb APIsJavaScript

TL;DR

For a non-arrow function registered with addEventListener(), the browser calls the listener with this set to the element on which the listener is registered. This is the same value as event.currentTarget, and it may differ from event.target, the descendant where the event originated. Arrow functions instead capture this lexically, and bind() can explicitly fix a regular function's this value.


The concept of this binding in event handlers

Understanding this in JavaScript

For regular functions, the value of this is determined primarily by how the function is called. Arrow functions are the exception: they capture this from the surrounding lexical scope.

this in event handlers

For a regular function passed to addEventListener(), this refers to the DOM element that received the listener. For example:

// Create a button element and append it to the DOM
const button = document.createElement('button');
button.id = 'myButton';
document.body.appendChild(button);
document.getElementById('myButton').addEventListener('click', function () {
console.log(this); // `this` refers to the 'myButton' element
});
button.click(); // Logs the button element

In this example, this inside the event handler refers to the button element that was clicked.

With delegation, the distinction is clearer: clicking a descendant changes event.target, but this and event.currentTarget still refer to the ancestor holding the listener.

list.addEventListener('click', function (event) {
console.log(this === event.currentTarget); // true
console.log(event.target); // The descendant that was clicked
});

Changing the value of this

There are several ways to change the value of this in event handlers:

Using bind()

The bind() method creates a new function that, when called, has its this keyword set to the provided value:

// Create a button element and append it to the DOM
const button = document.createElement('button');
button.id = 'myButton';
document.body.appendChild(button);
function handleClick() {
console.log(this); // Logs the object passed to bind()
}
const obj = { name: 'MyObject' };
document
.getElementById('myButton')
.addEventListener('click', handleClick.bind(obj));
button.click(); // Logs obj because handleClick was bound to obj using bind()

In this example, this inside handleClick refers to obj.

Using arrow functions

Arrow functions do not have their own this context; they inherit this from the surrounding lexical context:

// Create a button element and append it to the DOM
const button = document.createElement('button');
button.id = 'myButton';
document.body.appendChild(button);
const obj = {
name: 'MyObject',
handleClick: function () {
document.getElementById('myButton').addEventListener('click', () => {
console.log(this); // Logs obj
});
},
};
obj.handleClick();
button.click(); // This will log obj

In this example, this inside the arrow function refers to obj.

Assigning the context explicitly

You can also assign the context explicitly by using a variable:

// Create a button element and append it to the DOM
const button = document.createElement('button');
button.id = 'myButton';
document.body.appendChild(button);
const obj = {
name: 'MyObject',
handleClick: function () {
const self = this;
document.getElementById('myButton').addEventListener('click', function () {
console.log(self); // Logs obj
});
},
};
obj.handleClick();
button.click(); // This will log obj

In this example, self is used to capture the value of this from the outer function.

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise
Check your understanding Exercise

A user clicks the span inside the button. What relationship holds in the regular-function listener?

button.addEventListener('click', function (event) {
console.log(
this === button,
event.currentTarget === button,
event.target === button,
);
});