Home/Blog/Web Development/Understanding “this” in JavaScript
Home/Blog/Web Development/Understanding “this” in JavaScript

Understanding “this” in JavaScript

Ishrat Fatima
Apr 17, 2024
11 min read

In many programming languages like C++ or Java, the concept of this is often associated with object-oriented programming (OOP) paradigms. When we refer to this within a method or function in these languages, it typically points to the current instance of the class or object on which the method is being invoked. However, in JavaScript, the behavior of this differs significantly because its reference can change depending on the execution context. Understanding these dynamic context bindings is crucial for effectively utilizing this in JavaScript.

In this comprehensive blog, we take an in-depth look at the diverse contexts and behaviors of the this keyword to explain its usage. Covering global and function contexts, alongside specific scenarios such as arrow functions and method contexts, we provide clear guidance on understanding how this operates in each situation. We also explain implicit, explicit, and default binding, exploring methods like call(), bind(), and apply(). With detailed explanations and illustrative code samples, our goal is to provide you with the knowledge needed to effectively utilize the power of this in JavaScript.

Note: As it’s widely understood, JavaScript operates within two primary environments: Node.js and the browser. Throughout this blog, we’ll discuss JavaScript in general, explaining its behavior as it applies to both environments. We'll make specific distinctions whenever its behavior varies based on the executing environment. Similarly, where applicable, we’ll also pay careful attention to the variations in execution behavior between Javascript’s strict and non-strict modes.

Global context and this#

In a Node.js module, when JavaScript code is executing at the root level (outside of any function or block), this typically refers to module.exports.

For example, if we execute console.log(this) at the root level of a Node.js module, we’ll see that this refers to module.exports. In a module, module.exports initially begins as an empty object. As we add variables or methods to it, it evolves from an empty state to containing values. To confirm that this indeed refers to module.exports, let's append a function to it and then print both this and module.exports in the following code:

console.log('Value of `this` at the start:', this);
console.log('Value of `module.exports` at the start:', module.exports);
// Adding a function to module.exports
module.exports.myFunction = function() {
console.log('Just a function!');
};
// Print both this and module.exports after adding a function
console.log('\nValue of `this` after adding a function:', this);
console.log('Value of `module.exports` after adding a function:', module.exports);

However, in a browser environment, when JavaScript code is executing at the global level (outside of any function or block), this typically refers to the global window object.

Function context and this#

There are five primary function contexts in JavaScript that can affect the value of this.

Regular function context#

When a regular function is called in the global scope, this refers to the global object.

The global object depends on the JavaScript execution environment. In a web browser, it’s the window object, while in a Node.js environment, it’s the global object.

In the following code, we define a regular function named myFunction and invoke it within the global scope. Execute the code to verify that this points to the global object. This behavior is expected because we’ve configured the following widget to run within the Node.js environment:

//'use strict'
// Regular function context
function myFunction()
{
console.log('Value of `this` inside `myFunction`:',this)
}
myFunction()

Note: The this keyword is undefined inside a standalone function if the JavaScript code runs in strict mode. To observe the behavior of this in strict mode, remove the comment from 'use strict' in the code above and execute it.

Destructured function context#

When a method is extracted from an object using object destructuring and then invoked directly, this inside the function usually refers to the global object. This happens because the extracted function loses its original context and is no longer associated with the object from which it was extracted. Consequently, if the method depends on this to access properties or methods of the original object, it can result in unexpected behavior or errors.

In the following code, we have a user object containing the properties name and email, along with a method called displayProfile to log user details. Before proceeding, can you guess the output of the code without running it?

// Destructured function context
const user = {
name: 'Alice',
email: 'alice@example.com',
displayProfile() {
console.log(`--> Name: ${this.name}, Email: ${this.email}`)
console.log('--> Value of `this` inside the `displayProfile` function: \n',this)
},
};
// Calling function on the user object
console.log('Calling `displayProfile` on the `user` object:')
user.displayProfile()
// Destructuring the displayProfile method
const { displayProfile } = user
// Calling the displayProfile method
console.log('\nCalling `displayProfile` after de-structuring:')
displayProfile()

Initially, we call displayProfile directly on the user object, which correctly accesses its properties. You might have rightly guessed that after destructuring the displayProfile method from the user object, calling the function results in this pointing to the global object. Because the global object does not possess the name and email properties, any references to them within the function return undefined.

Constructor function context#

When a function is used as a constructor (with the new keyword), this refers to the newly created instance of the object.
In the following code, we define a constructor function Product to create objects with a price property and a getInfo method to retrieve the price. When invoked with the new keyword, this inside the constructor refers to the newly created instance of the object. Therefore, when getInfo is called on the laptop object, this points to the laptop object itself, allowing access to its price property.

// Constructor function context
function Product(price) {
this.price = price;
this.getInfo = function() {
console.log('--> Value of `this` inside the `getInfo` function: \n',this)
return this.price
};
}
const laptop = new Product(1200);
laptop.getInfo()

Object literal method context#

When a method is defined within an object literal, this refers to the object itself, allowing access to other properties and methods of the same object using this.
In the following code, we define an object literal named recipe with a title property and a displayTitle method to log the recipe title. When displayTitle is invoked on the recipe object, this inside the method refers to the recipe object itself. Therefore, this.title accesses the title property of the recipe object, allowing the method to correctly display the recipe title.

//Object literal method context
const recipe = {
title: 'Chocolate Cake',
displayTitle() {
console.log(`--> Recipe: ${this.title}`);
console.log('--> Value of `this` inside the `displayTitle` function: \n',this)
},
};
recipe.displayTitle();

Class method context#

When a function is called as a method of a class object, this refers to the class instance that owns the method.
In the following code, we define a BankAccount class with a constructor to initialize the owner and balance properties. When an instance of BankAccount is created using the new keyword, this inside the constructor refers to the newly created instance. Similarly, the deposit method of the class operates on the instance’s properties, and this within the method points to the instance itself. Therefore, calling deposit on myAccount correctly updates the balance property of the instance.

// Class method context
class BankAccount {
constructor(owner, balance) {
this.owner = owner
this.balance = balance
console.log('--> Value of `this` inside the `constructor`: \n',this)
}
deposit(amount) {
this.balance += amount;
console.log(`Deposit of $${amount} successful. New balance: $${this.balance}`)
console.log('--> Value of `this` inside the `deposit` function: \n',this)
}
}
const myAccount = new BankAccount('John Doe', 1000)
myAccount.deposit(500)

The following diagram provides a summary of the various function contexts:

Summary of function contexts
Summary of function contexts

The arrow function and this#

In JavaScript, arrow functions work a bit differently with the this keyword compared to regular functions. An arrow function doesn’t make its own this. Instead, it uses the this from where it is already written in its surroundings. This makes arrow functions practical in scenarios where keeping track of this is important, such as handling events or using callbacks. When an arrow function is passed as a callback, the this value is preserved and remains the same throughout the callback function’s execution. This preservation of the this value holds true even if the callback is executed later or triggered by a future event.

For example, consider the following code, where we simulate a user login. The User object’s login method uses an arrow function as a callback within the setTimeout function. Despite the delay, the value of this is valid and refers to the User object, ensuring the isLoggedIn property is updated correctly.

const User = {
name: 'John',
isLoggedIn: false,
login() {
// Set isLoggedIn to true after a 2-second delay
setTimeout(() => {
this.isLoggedIn = true;
console.log(`${this.name} has logged in successfully!`);
}, 2000);
}
};
User.login();

What if a regular function is used within setTimeout instead of an arrow function? We’ve replicated the same behavior in the following code. Can you predict its output without executing it?

const User = {
name: 'John',
isLoggedIn: false,
login() {
// Set isLoggedIn to true after a 2-second delay
setTimeout(function() {
this.isLoggedIn = true;
console.log(`${this.name} has logged in successfully!`);
console.log('\n--> Value of `this` inside the `callback` function: \n',this)
}, 2000);
}
};
User.login();

If a regular function is used within setTimeout instead of an arrow function, this points to the Timeout object rather than the User object.
As explained before, this happens because regular functions create their own this context, which is determined by how the function is called. When setTimeout calls the callback function, it does so in the context of the Timeout object. This also means that the callback is executed outside the scope of the User object. Therefore, within the setTimeout callback function, this refers to the Timeout object, not the User object. Consequently, attempting to update the isLoggedIn property fails, leading to unexpected behavior.

Difference between arrow and regular function callbacks
Difference between arrow and regular function callbacks


The following table summarizes the behavior of the this keyword in different function contexts we’ve explored so far:

Context

Behavior

Global context

The this keyword refers to module.exports (Node.js) or global window (browser) object.

Function context

Subcontexts:

Regular function: The this keyword refers to the global (Node.js) or window (browser) object.

Destructured Function: The global keyword (Node.js) or window (browser) object.

Constructor function: The this keyword refers to a newly created instance of the object.

Object literal method: The this keyword refers to the object itself.

Class method: The this keyword refers to the class instance that owns the method.

Arrow function

Retains this from the lexical context where it’s defined.

Implicit, explicit, and default binding of this#

Implicit binding automatically assigns this inside a function to the object preceding the dot during the function call. For example, when invoking a function using dot notation, like object.method(), this refers to object.

Explicit binding involves manually defining this within a function using methods like call(), apply(), or bind(). These methods allow precise control over this, irrespective of how the function is called. This intentional step is helpful when handling changing function environments or when we want to use the same function with different objects.

Explicit binding methods
Explicit binding methods


To understand explicit binding methods better, let’s explore how bind(), call(), and apply() can be used to precisely control this inside functions.

  • bind(): This generates a fresh function where this is explicitly defined by passing an object as an argument. When this newly created function is called, this is bound to the provided object.
    For example, in the following code snippet, the function calculateTotal calculates the total price of items in a shopping cart. Note that calculateTotal is a standalone function. If called, the context of this inside calculateTotal would typically refer to the global object, leading to potential errors or unexpected behavior.
    To address this concern, the bind() method is utilized. The bind() method is used to create two new functions, calculateTotalForCart1 and calculateTotalForCart2, by generating fresh instances of the original calculateTotal function. These new functions explicitly define this by passing an object as an argument, ensuring that when invoked, they maintain a context bound to cart1 and cart2, respectively.

function calculateTotal(currencySymbol) {
let total = 0;
this.items.forEach(item => {
total += item.price;
});
console.log(`Total: ${currencySymbol}${total}`);
}
const cart1 = {
items: [
{ name: 'Shirt', price: 20 },
{ name: 'Jeans', price: 30 },
{ name: 'Shoes', price: 50 }
]
};
const cart2 = {
items: [
{ name: 'Book', price: 15 },
{ name: 'Pen', price: 5 },
{ name: 'Notebook', price: 10 }
]
};
// Using explicit binding with the bind() method to create new functions bound to shopping carts
const calculateTotalForCart1 = calculateTotal.bind(cart1, '$');
const calculateTotalForCart2 = calculateTotal.bind(cart2, '€');
// Using the newly bound functions to calculate total for different shopping carts
calculateTotalForCart1(); // Total: $100
calculateTotalForCart2(); // Total: €30
  • call(): This immediately calls the method, and this is bound to the object passed as an argument. It’s useful when we want to invoke a function with a specific this context without creating a new function.
    In the following code, the call() method is used to explicitly specify the context (this value) when invoking the sendEmail function for user1 and user2, ensuring emails are sent immediately to the correct recipients.

function sendEmail(message, priority) {
console.log(`Sending email to ${this.email} with message: ${message} and priority: ${priority}`);
}
const user1 = {
email: 'user1@example.com',
name: 'Alice'
};
const user2 = {
email: 'user2@example.com',
name: 'Bob'
};
const message = 'Hello, this is a reminder email.';
const priority = 'high';
// Using explicit binding with the call() method to send emails to different users
sendEmail.call(user1, message, priority);
sendEmail.call(user2, message, priority);
  • apply(): This is similar to call() because it immediately calls the method, and this is bound to the object passed as an argument. The main difference between call() and apply() lies in how arguments are passed to the function being invoked. With call(), arguments are passed individually and separated by commas, whereas with apply(), arguments are passed as an array.
    In the following code, sendEmail.call() directly invokes sendEmail for user1, with message and priority passed as arguments. Conversely, sendEmail.apply() achieves the same result but accepts the arguments within the array [message, priority].

function sendEmail(message, priority) {
console.log(`Sending email to ${this.email} with message: ${message} and priority: ${priority}`);
}
const user1 = {
email: 'user1@example.com',
name: 'Alice'
};
const user2 = {
email: 'user2@example.com',
name: 'Bob'
};
const message = 'Hello, this is a reminder email.';
const priority = 'high';
// Using explicit binding with the call() method to send emails to different users
sendEmail.call(user1, message, priority);
sendEmail.call(user2, message, priority);
// Using explicit binding with the apply() method to send emails to different users
sendEmail.apply(user1, [message, priority]);
sendEmail.apply(user2, [message, priority]);

Default binding sets the this keyword to the global object when a function is called on its own, without any specific context. This happens in the global scope when a function is invoked directly, without using dot notation or explicit binding methods like call(), apply(), or bind().

In the following example, when we first invoke announce() without a specific context, this is set to the global object by default. So, when it tries to find this.department, it can’t, and it prints “Attention, undefined department!”. To fix this, we use call(department) in the second announce() call. It binds the department object to this so it can find the department property.

//'use strict'
function announce() {
console.log(`Attention, ${this.department} department!`);
}
const department = {
department: 'Marketing',
announce: announce
};
announce(); // 'this' inside announce is by default attached to the 'global' object
announce.call(department) // 'this' inside announce is bound to the 'department' object

Note: Uncomment 'use strict' in the provided code to observe that accessing this.department will result in a runtime exception. In JavaScript’s strict mode, this defaults to undefined in the global function context.

The following table summarizes the behavior of the this keyword in different binding contexts, aiding you in referencing and applying this more effectively:

Context

Behavior

Implicit binding

The this keyword inside a function is automatically assigned to the object before the dot during the function call.

Explicit binding

Methods like call(), apply(), and bind() are employed to manually designate this within a function.

Default binding

When a function is invoked independently, this defaults to the global (Node.js) or window (browser) object.

We hope that through this exploration, you’ve gained a clearer understanding of the often confusing concept of using this in various execution contexts and platforms within JavaScript. Our practical code examples were designed to offer you hands-on explanations of these concepts.

Continuing your journey#

We highly encourage you to continue your learning journey and deepen your understanding of JavaScript by exploring our extensive catalog of courses and learning paths on Educative. Our platform offers comprehensive resources tailored to support learners at every stage of their JavaScript proficiency. Head over to educative.io to explore how our practical content can help you unlock your full potential as a JavaScript developer. Some of the top courses available on our website include:

Zero to Hero in Front-end Web Development

Cover
Zero to Hero in Front-end Web Development

Front-end developers are in high demand because tech companies are always looking for ways to make their apps look and work better. To learn front-end web development, you must know HTML, CSS, and JavaScript. This Skill Path is the perfect place to start front-end development if you don't have a programming background. It comprehensively introduces you to the most important parts of the web, particularly the front-end part of web development. You will start with an introduction of basic terms in the web development domain. As you move on, you'll learn the basic pillars of front-end development, i.e., HTML, CSS, and JavaScript. Next, you’ll work on smaller projects to get a strong handle on HTML, CSS, and JavaScript. Lastly, you will get a full, step-by-step explanation of how to get a website up and running on the Internet. By the end of this Skill Path, you will know how to make functional websites and web applications.

51hrs
Beginner
46 Challenges
58 Quizzes


  

Free Resources