JavaScript this, call(), apply(), and bind() Explained

If you have spent any time reading JavaScript code, you have seen this pop up in unexpected places and wondered what it actually refers to. And then you stumble across call(), apply(), or bind() and the confusion compounds.
This article breaks all of it down — this first, then the three methods that let you control it.
What is this?
The simplest way to think about this is: this refers to whoever is calling the function right now.
It is not fixed when you write the function. It is decided at the moment the function is called, based on what is to the left of the dot.
function greet() {
console.log("Hello, I am " + this.name);
}
This function uses this.name. But whose name? That depends entirely on how and where greet is called. The function itself does not know yet — it finds out at call time.
this in the Global Context
When you call a function on its own, with nothing to the left of the dot, this refers to the global object. In a browser that is window. In Node.js it is global.
function showThis() {
console.log(this);
}
showThis(); // global object (window in browser, global in Node.js)
In strict mode, this is undefined instead of the global object, which is one reason strict mode exists — it prevents accidental writes to global variables.
"use strict";
function showThis() {
console.log(this); // undefined
}
showThis();
this Inside an Object
When a function is called as a method on an object — meaning there is an object to the left of the dot — this refers to that object.
const person = {
name: "Arjun",
age: 21,
greet() {
console.log("Hi, I am " + this.name + " and I am " + this.age + " years old.");
},
};
person.greet(); // Hi, I am Arjun and I am 21 years old.
person is to the left of the dot when greet() is called, so this inside greet is person. That means this.name is "Arjun" and this.age is 21.
Change the caller, and this changes with it:
const person1 = {
name: "Arjun",
greet() {
console.log("Hi, I am " + this.name);
},
};
const person2 = {
name: "Priya",
};
// person2 borrows person1's greet method
person2.greet = person1.greet;
person1.greet(); // Hi, I am Arjun — this = person1
person2.greet(); // Hi, I am Priya — this = person2
The exact same function, called on two different objects, produces two different results because this takes the value of whatever object is doing the calling.
The Caller Relationship
object.method()
───────┬───────
│
▼
"this" inside method = that object
┌──────────────┐ ┌─────────────────────────┐
│ person1 │──────► │ greet() │
│ name:"Arjun"│ calls │ this.name → "Arjun" │
└──────────────┘ └─────────────────────────┘
┌──────────────┐ ┌─────────────────────────┐
│ person2 │──────► │ greet() (same fn) │
│ name:"Priya"│ calls │ this.name → "Priya" │
└──────────────┘ └─────────────────────────┘
Losing this
A common source of bugs is when a method is pulled out of its object and called on its own. The object context is lost.
const person = {
name: "Arjun",
greet() {
console.log("Hi, I am " + this.name);
},
};
person.greet(); // Hi, I am Arjun — works fine
// Storing the method in a variable loses the object context
const fn = person.greet;
fn(); // Hi, I am undefined — this is now global, which has no 'name'
This is the exact problem that call, apply, and bind are designed to solve. They let you explicitly tell a function what this should be when it runs.
call()
call() lets you invoke a function immediately and specify what this should be. Any arguments you want to pass to the function go after the first argument.
function introduce(city, hobby) {
console.log(`I am \({this.name}, from \){city}. I like ${hobby}.`);
}
const person1 = { name: "Arjun" };
const person2 = { name: "Priya" };
// Call introduce with person1 as 'this'
introduce.call(person1, "Mumbai", "coding");
// I am Arjun, from Mumbai. I like coding.
// Call the same function with person2 as 'this'
introduce.call(person2, "Delhi", "reading");
// I am Priya, from Delhi. I like reading.
The function introduce was never on either object. call() borrowed it and ran it as if it were, with the specified object as this.
A practical case — borrowing a method from one object to use on another:
const employee = {
name: "Arjun",
department: "Engineering",
describe() {
console.log(`\({this.name} works in \){this.department}.`);
},
};
const contractor = {
name: "Rohan",
department: "Design",
};
// contractor doesn't have describe() — borrow it from employee
employee.describe.call(contractor);
// Rohan works in Design.
apply()
apply() does the same thing as call() — it invokes a function immediately with a specified this. The only difference is how you pass arguments: instead of a comma-separated list, you pass an array.
function introduce(city, hobby) {
console.log(`I am \({this.name}, from \){city}. I like ${hobby}.`);
}
const person1 = { name: "Arjun" };
const person2 = { name: "Priya" };
// call() — arguments one by one
introduce.call(person1, "Mumbai", "coding");
// apply() — arguments as an array
introduce.apply(person1, ["Mumbai", "coding"]);
// Both produce the same output:
// I am Arjun, from Mumbai. I like coding.
Where apply() becomes genuinely useful is when you already have your arguments collected in an array:
function sum(a, b, c) {
console.log(`\({this.label}: \){a + b + c}`);
}
const context = { label: "Total" };
const numbers = [10, 20, 30];
// Spread the array as individual arguments
sum.apply(context, numbers);
// Total: 60
Another classic use — finding the max value in an array using Math.max, which does not accept an array directly:
const scores = [88, 92, 75, 96, 61];
const max = Math.max.apply(null, scores);
console.log("Highest score:", max); // Highest score: 96
(null is passed as this because Math.max does not use this at all — we just need the array-spreading behavior.)
bind()
bind() is different from call() and apply() in one important way: it does not call the function immediately. Instead it returns a new function with this permanently fixed to whatever you specify. You call that new function whenever you need it.
function greet(greeting) {
console.log(`\({greeting}, I am \){this.name}.`);
}
const person = { name: "Arjun" };
// bind() returns a new function — does NOT call it yet
const greetArjun = greet.bind(person);
// Call it later, whenever needed
greetArjun("Hello"); // Hello, I am Arjun.
greetArjun("Good day"); // Good day, I am Arjun.
No matter how many times you call greetArjun, or where you call it from, this will always be person. It is permanently bound.
This is particularly useful when passing methods as callbacks, where the object context would otherwise be lost:
const timer = {
name: "Countdown",
start() {
console.log(this.name + " started.");
},
};
// Without bind — 'this' is lost when the method is passed as a callback
setTimeout(timer.start, 1000); // undefined started.
// With bind — 'this' is locked to timer
setTimeout(timer.start.bind(timer), 1000); // Countdown started.
You can also pre-fill arguments with bind() — a technique called partial application:
function multiply(a, b) {
return a * b;
}
// Create a new function with 'a' pre-set to 2
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
console.log(double(8)); // 16
console.log(double(12)); // 24
Comparison Table
call() |
apply() |
bind() |
|
|---|---|---|---|
| Calls function immediately? | Yes | Yes | No — returns a new function |
| How arguments are passed | One by one: fn.call(obj, a, b) |
As an array: fn.apply(obj, [a, b]) |
One by one: fn.bind(obj, a, b) |
| Returns | The function's return value | The function's return value | A new bound function |
| When to use | Borrow a method, run it now | Same as call, but you have an array | Fix this for later use / callbacks |
Bringing It All Together
Here is one complete example showing all three in action:
// A standalone function that uses 'this'
function displayProfile(city, hobby) {
console.log(
`Name: \({this.name} | Age: \){this.age} | City: \({city} | Hobby: \){hobby}`
);
}
const user1 = { name: "Arjun", age: 21 };
const user2 = { name: "Priya", age: 24 };
const user3 = { name: "Rohan", age: 19 };
// call() — run immediately, arguments one by one
displayProfile.call(user1, "Mumbai", "coding");
// Name: Arjun | Age: 21 | City: Mumbai | Hobby: coding
// apply() — run immediately, arguments as array
const user2Args = ["Delhi", "painting"];
displayProfile.apply(user2, user2Args);
// Name: Priya | Age: 24 | City: Delhi | Hobby: painting
// bind() — create a reusable function locked to user3
const showRohan = displayProfile.bind(user3, "Bangalore");
showRohan("chess"); // Name: Rohan | Age: 19 | City: Bangalore | Hobby: chess
showRohan("cycling"); // Name: Rohan | Age: 19 | City: Bangalore | Hobby: cycling
Trying It All Together — Practical Demo
Let's build a small realistic scenario: a gradeReport function that needs to work for different students.
function gradeReport(subject1, subject2, subject3) {
const total = subject1 + subject2 + subject3;
const average = (total / 3).toFixed(1);
console.log(`\({this.name} (\){this.course}): \({subject1}, \){subject2}, \({subject3} → Avg: \){average}`);
}
const studentA = { name: "Arjun", course: "Computer Science" };
const studentB = { name: "Priya", course: "Mathematics" };
const studentC = { name: "Rohan", course: "Physics" };
// call() — run the report for studentA right now
gradeReport.call(studentA, 88, 92, 79);
// Arjun (Computer Science): 88, 92, 79 → Avg: 86.3
// apply() — Priya's scores already in an array
const priyaScores = [91, 85, 94];
gradeReport.apply(studentB, priyaScores);
// Priya (Mathematics): 91, 85, 94 → Avg: 90.0
// bind() — create a dedicated report function for Rohan
const rohanReport = gradeReport.bind(studentC);
rohanReport(76, 88, 82);
// Rohan (Physics): 76, 88, 82 → Avg: 82.0
// Later in the code, same bound function still works
rohanReport(90, 95, 88);
// Rohan (Physics): 90, 95, 88 → Avg: 91.0
Quick Recap
thisrefers to the object that is calling the function — it is determined at call time, not at write timeInside an object method,
thisis the object to the left of the dotA method pulled out of its object and called standalone loses its
thiscontextcall(obj, arg1, arg2)— calls the function immediately withobjasthis, arguments passed one by oneapply(obj, [arg1, arg2])— same ascallbut arguments are passed as an arraybind(obj, arg1)— does not call the function, returns a new function withthispermanently fixed
Comments
Join the discussionNo comments yet. Be the first to comment.
