Explain the concept of `this` binding in event handlers
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 DOMconst 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); // trueconsole.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 DOMconst 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 DOMconst 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 DOMconst 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.