The Magic of this, call(), apply(), and bind() in JavaScript

In JavaScript, this is one of the most confusing concepts for beginners. Sometimes it refers to an object, sometimes to the global scope, and sometimes it becomes undefined.
If you've ever passed a function as a callback and suddenly this becomes undefined, you've already experienced this problem.
To control the value of this, JavaScript provides three important methods: call(), apply(), and bind().
In this article, we’ll explore how this works and how these three methods help us control the function context.
Understanding this in JavaScript
In JavaScript, this is a special keyword that lets us access the current execution context.
For example, if we log this in the browser console, it refers to the global window object. But if we log this in a Node.js project, it refers to the global object.
If we log this inside an object method, it refers to the object that calls the method.
let userObj = {
name : "Raaju",
age : 33,
introduce : function(){
console.log(`Hey, my name is \({this.name} and i am \){this.age} years old`)
}
}
userObj.introduce()
output :
Hey, my name is Raaju and i am 33 years old
Here, this refers to the userObj object because the method is called using that object.
this Inside a Function
Now let's see how this behaves inside a normal function.
function refD(){
console.log(this)
}
refD()
output:
<ref *1> Object [global] {
global: [Circular *1],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
structuredClone: [Function: structuredClone],
atob: [Getter/Setter],
btoa: [Getter/Setter],
performance: [Getter/Setter],
fetch: [Function: fetch],
crypto: [Getter]
}
In this case, this refers to the global object because the function is called directly and not as part of any object.
The Problem: Losing the this Context
However, this magical context can sometimes be lost when a function is executed outside of its object.
For example:
const user = {
name: "Abdul",
greet() {
console.log(this.name);
}
};
const greetFunction = user.greet;
greetFunction();
Output:
undefined
In the above example, the context gets lost because we detached the function from its original object.
When greet() was inside the user object, this referred to user. But once we stored the function in another variable and called it separately, this no longer referred to the object.
This is where call(), apply(), and bind() become useful.
Using call()
call() allows us to invoke a function while explicitly specifying what this should refer to.
The first parameter passed to call() becomes the value of this, and the remaining parameters are passed as arguments to the function.
For example:
const person = {
name: "Aman"
};
function greet(age) {
console.log(`Hi, I am \({this.name} and I am \){age} years old`);
}
greet.call(person, 25);
Output:
Hi, I am Aman and I am 25 years old
Using apply()
apply() works almost the same as call(). The main difference is how arguments are passed.
In apply(), the arguments are passed as an array.
For example:
const person = {
name: "Aman"
};
function greet(age) {
console.log(`Hi, I am \({this.name} and I am \){age} years old`);
}
greet.apply(person, [25]);
Output:
Hi, I am Aman and I am 25 years old
So the only difference between call() and apply() is how arguments are provided.
Using bind()
bind() works a little differently.
Instead of executing the function immediately, bind() returns a new function where this is permanently bound to the provided object.
For example
const person = {
name: "Aman"
};
function greet(age) {
console.log(`Hi, I am \({this.name} and I am \){age} years old`);
}
const greetPerson = greet.bind(person, 25);
greetPerson();
Output:
Hi, I am Aman and I am 25 years old
Here, bind() created a new function where this always refers to person.
Difference Between call(), apply(), and bind()
| Method | Arguments | Executes Immediately |
|---|---|---|
| call() | Arguments passed separately | Yes |
| apply() | Arguments passed as an array | Yes |
| bind() | Arguments passed separately | No (returns a new function) |
Conclusion
Understanding this is important when working with JavaScript functions and objects.
Sometimes the context of this can be lost when a function is called outside its original object. To control the value of this, JavaScript provides three useful methods: call(), apply(), and bind().
call()executes the function immediately and lets us specify thethisvalue.apply()works likecall()but accepts arguments as an array.bind()creates a new function with a permanently boundthis.
Mastering these methods will help you write more flexible and predictable JavaScript code.