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
andnon-strict
modes.
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.exportsmodule.exports.myFunction = function() {console.log('Just a function!');};// Print both this and module.exports after adding a functionconsole.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.
There are five primary function contexts in JavaScript that can affect the value of this.
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 theglobal
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 contextfunction myFunction(){console.log('Value of `this` inside `myFunction`:',this)}myFunction()
Note: The
this
keyword isundefined
inside a standalone function if the JavaScript code runs instrict
mode. To observe the behavior ofthis
instrict
mode, remove the comment from'use strict'
in the code above and execute it.
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 contextconst 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 objectconsole.log('Calling `displayProfile` on the `user` object:')user.displayProfile()// Destructuring the displayProfile methodconst { displayProfile } = user// Calling the displayProfile methodconsole.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
.
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 contextfunction 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()
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 contextconst recipe = {title: 'Chocolate Cake',displayTitle() {console.log(`--> Recipe: ${this.title}`);console.log('--> Value of `this` inside the `displayTitle` function: \n',this)},};recipe.displayTitle();
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 contextclass BankAccount {constructor(owner, balance) {this.owner = ownerthis.balance = balanceconsole.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:
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 delaysetTimeout(() => {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 delaysetTimeout(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.
The following table summarizes the behavior of the this
keyword in different function contexts we’ve explored so far:
Context | Behavior |
Global context | The |
Function context | Subcontexts: |
Regular function: The | |
Destructured Function: The | |
Constructor function: The | |
Object literal method: The | |
Class method: The | |
Arrow function | Retains |
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.
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 cartsconst calculateTotalForCart1 = calculateTotal.bind(cart1, '$');const calculateTotalForCart2 = calculateTotal.bind(cart2, '€');// Using the newly bound functions to calculate total for different shopping cartscalculateTotalForCart1(); // Total: $100calculateTotalForCart2(); // 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 userssendEmail.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 userssendEmail.call(user1, message, priority);sendEmail.call(user2, message, priority);// Using explicit binding with the apply() method to send emails to different userssendEmail.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' objectannounce.call(department) // 'this' inside announce is bound to the 'department' object
Note: Uncomment
'use strict'
in the provided code to observe that accessingthis.department
will result in a runtime exception. In JavaScript’s strict mode,this
defaults toundefined
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 |
Explicit binding | Methods like |
Default binding | When a function is invoked independently, |
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.
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
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.
Free Resources