Table of contents
The JavaScript this
keyword is a dynamic powerhouse, but it can be a subtle quirk that challenges many JavaScript developers. Understanding how it works is crucial, and it shouldn't be a hard nut to crack.
In this article, we'll delve into the this
keyword in JavaScript. My goal is to explain it in simple terms for easy comprehension. However, a strong grasp of JavaScript fundamentals, including variables, functions, objects, and scope (global, local, and function), as well as DOM manipulation, is a fundamental prerequisite for better understanding the this
keyword.
So, if you're ready, let's jump in and get started!
What is this
Keyword?
The this
keyword in JavaScript usually refers to an object it is inside of at a given time. You can think of it like a pronoun in a sentence—it takes on the meaning of whatever noun it's replacing.
The this
keyword helps you access and work with the properties and methods of the current object or context in your code. This makes it a valuable tool for managing and manipulating data.
Note that this
keyword can change its meaning depending on where and how it's used. Also, note that this
is not a variable; it is a keyword, so it cannot be redeclared or its value reassigned.
How does this
work in JavaScript?
The this
keyword can be a bit tricky to understand because it is context-dependent. Unlike a variable that can have a fixed value, this
keyword is dynamic. To understand how it works, you need to consider the context in which it is used.
Let's consider some key contexts:
Global Context
In the global context, this
refers to the global object. In a web browser, this is often the window
object. In Node.js, it's the global
object.
Look at an example:
console.log(this); // 'this' refers to the global object (window in a web browser)
console.log(window === this); // This will return true in a web browser
When you run this code in a web browser's JavaScript console, you'll see that this
refers to the window
object, and the comparison between window
and this
will be true, indicating that they are the same object in the global context.
Function Context (Regular Functions)
When this
keyword is inside a regular function (or function declaration), this
refers to the global
object, but this behaviour can change depending on how the function is invoked.
For example:
function greetings() {
console.log(this); // "this" here, points to the global object (window in a browser)
}
greetings();
However, when this
keyword is inside an object, it behaves differently.
Look at the example below:
const person = {
name: "John",
sayHi: function() {
console.log(this.name); // "this" points to the 'person' object
}
};
person.sayHi();
In the example above, the sayHi
function is part of the "person" object. When you invoke it using person.sayHi()
, this
inside the function refers to the "person" object. This allows you to access the "name" property of that object.
There is a method called the bind()
that you can use to manipulate (to bind) the value of this
in a regular function.
You can read more about it in MDN web docs.
Event Handlers
The this
keyword enables dynamic interaction with the DOM, particularly when managing multiple elements of the same type using event handlers.
// Select all elements with the class "myButton" and store them in the 'buttons' variable as a NodeList
const buttons = document.querySelectorAll(".myButton");
// Iterate over each button element using the 'forEach' method
buttons.forEach(function(button) {
// For each button, add a click event listener
button.addEventListener("click", function() {
// Within the event handler function, 'this' refers to the button that triggered the click event
console.log(this.textContent); // Outputs the text content of the clicked button
});
});
The above code demonstrates how the this
keyword is used in event handlers to refer to the specific DOM element that triggered the event.
Arrow Functions
Arrow functions behave differently from regular functions when it comes to this
keyword. They inherit the value of this
from their lexical (surrounding) scope. That may sound confusing, but here's what that means:
// Create an object 'person' with a 'name' property and a 'sayHello' method.
const person = {
name: "Alice",
sayHello: () => {
// Inside the arrow function, 'this' does not refer to 'person'.
// It refers to the global object (e.g., 'window' in a browser or 'global' in Node.js).
console.log(this.name); // 'this' refers to the global object, not 'person'
}
};
Did you observe the difference?
Key differences between an arrow function and a regular function in this context:
- Arrow Function
In an arrow function, such as the one defined in sayHello
, this
does not have its own context. It inherits this
from its surrounding lexical (enclosing) scope. In this case, it inherits the global this
, so this.name
refers to the global name
property, which is typically undefined, leading to an error.
- Regular Function
If you were to define sayHello
as a regular function, it would have its own this
context, which would correctly refer to the person
object. Therefore, console.log(
this.name
)
would output "Alice," as you would expect.
Look at how the code would look with a regular function:
const person = {
name: "Alice",
sayHello: function() {
// Inside a regular function, 'this' refers to the object 'person'.
console.log(this.name); // 'this' correctly refers to 'person'
}
};
Please take note of this difference, especially when you're working with arrow functions in your projects.
Constructor Functions
Let me assume you haven't fully grasped the concept of constructor functions.
In JavaScript, a constructor function is a unique type of function that is used to create and initialize objects with a consistent structure and behavior.
Constructor functions are often used in JavaScript for object-oriented programming and are particularly useful when you need to create multiple instances of objects with similar properties and methods.
Look at how you define a constructor function:
function Person(name, age) {
this.name = name;
this.age = age;
}
Notice the first letter of the function name Person
in an uppercase. That's how constructor functions are usually defined.
So, how does this
keyword work in the constructor function?
Within a constructor function, the this
keyword points to the object that will be created when you use the constructor function. In other words, it acts as a placeholder for the future object and lets you assign properties to that object.
An example will explain it better:
function Friend(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
}
const firstFriend = new Friend("Gideon", 30);
const secondFriend = new Friend("Emeka", 25);
firstFriend.sayHi(); // Outputs: "Hi, my name is Alice and I'm 30 years old."
secondFriend.sayHi(); // Outputs: "Hi, my name is Bob and I'm 25 years old."
In this example, the this
keyword is used to set the name
and age
properties of each Friend
object and it's also used within the sayHi
method to access those properties when the method is called on a specific object instance.
If you still want to learn more about constructor functions, Read this article: Introduction to JavaScript Constructor Functions.
Common Pitfalls and Best Practices
You have understood the this
keyword for writing clean and bug-free code. However, it's crucial to be aware of common mistakes, some of which can be subtle. Pay close attention, as I'll also discuss some best practices. Now, let's dive in.
Using the this
keyword out of context
Some developers sometimes misuse the this
keyword. This can happen when a function is called without the expected context or when nested functions are involved.
Let's see an example:
const obj = {
value: 42,
printValue: function () {
setTimeout(function () {
console.log(this.value); // 'this' refers to the global object (e.g., 'window')
}, 1000);
},
};
To avoid this, what you can do is to save the outer this
context in a variable (const self = this
) or using arrow functions, which capture the surrounding (lexical scope) this
context.
Here's what I mean:
function Counter() {
this.count = 0;
// Save the outer "this" context in a variable
const self = this;
setInterval(function () {
// Access the count property using the saved "this" context
console.log("Count:", self.count);
self.count++;
}, 1000);
}
const myCounter = new Counter();
In this example, the self
variable is used to store the outer this
context within the constructor function. Inside the setInterval
callback function, self.count
is correctly accessed to update and display the count. This ensures that the this
context remains the same throughout the code.
Failure to properly bind a method
It's sometimes possible to forget to explicitly bind the this
context when using methods as event handlers or passing them as callbacks to functions like addEventListener
, setTimeout
, or setInterval
. But this is a great mistake and a bit common.
In JavaScript, when you use a method as an event handler or pass it as a callback, the context in which it is executed may change, causing unexpected behaviour. However, you can avoid this just by binding the method to a specific this
context.
You'll understand it better with an example. Let's say you have an object with a method like this:
const myObject = {
value: 42,
printValue: function () {
console.log(this.value);
}
};
If you want to use printValue
as an event handler or callback, you should ensure that it retains the myObject
context by binding it:
// Binding the method to the object
const boundPrintValue = myObject.printValue.bind(myObject);
// Using the bound method as an event handler
someElement.addEventListener('click', boundPrintValue);
By binding the printValue
method to myObject
, you prevent it from losing its intended context and ensure that it correctly accesses myObject
's properties and functions.
Conclusion
In this simplified guide, you've learned about the this
keyword: what it is, how to use it, and where to use it. This is an essential skill for any JavaScript developer. Throughout this guide, we've explored different use cases of the this
keyword in various contexts, ranging from global and function scope to object methods, event handlers, and constructors. We've also discussed how arrow functions differ in their treatment of the this
keyword and when it's appropriate to use them.
You've been exposed to common pitfalls to avoid and best practices to adopt. The truth is, JavaScript's this
keyword may initially appear confusing, but with deliberate practice, you can fully harness its potential to create robust and flexible applications.