If you've spent any time writing JavaScript, you've probably hit this wall at least once:
TypeError: Cannot read properties of undefined (reading 'name')
And somewhere in the stack trace, a function is using this — and it has absolutely no idea what it's pointing at.
You're not alone. this is one of the most misunderstood concepts in JavaScript. But once it clicks, it unlocks a whole new level of writing clean, reusable, and flexible code.
In this article, we'll cover:
- What
thisactually means (and why it's confusing) - How
call(),apply(), andbind()give you control over it - Real-world examples for each
- A comparison table so you never mix them up again
Let's get into it. 🚀
1. What Is this in JavaScript?
Here's the simplest way to understand this:
thisrefers to the object that is currently calling the function.
Think of it like a caller ID. When a function runs, JavaScript looks at who called it and assigns that as this.
const person = {
name: "Jai",
greet: function () {
console.log("Hello, I am " + this.name);
}
};
person.greet(); // Hello, I am Jai
Here, person is calling greet(), so this inside greet refers to person.
Simple enough. But it gets slippery fast.
2. this in a Standalone Function (Where It Gets Weird)
What happens when you call a function without an object?
function showThis() {
console.log(this);
}
showThis();
| Context | Value of this
|
|---|---|
| Browser (non-strict mode) |
window object |
| Browser (strict mode) | undefined |
| Node.js |
global object |
⚠️ Key takeaway: Regular standalone functions don't automatically know their "owner." The value of this depends entirely on how the function is called — not where it's defined.
3. this Inside an Object Method
When a function lives inside an object and gets called via that object, this points to that object.
const car = {
brand: "Tesla",
showBrand: function () {
console.log(this.brand);
}
};
car.showBrand(); // Tesla
this → car
Clean and predictable. The problem arises when you detach that method from its object.
const detached = car.showBrand;
detached(); // undefined — `this` lost its context!
This is where call(), apply(), and bind() come to the rescue.
4. call() — Borrow a Function and Run It Now
call() lets you invoke a function while explicitly setting what this should be. Arguments are passed individually.
Syntax
functionName.call(thisArg, arg1, arg2, ...)
Example
const person1 = { name: "Jai" };
function greet(city) {
console.log("Hello " + this.name + " from " + city);
}
greet.call(person1, "Kanpur");
// Hello Jai from Kanpur
Even though greet() has nothing to do with person1, call() says: "Run this function as if person1 is the caller."
Common Use Case: Method Borrowing
const student1 = {
name: "Aman",
introduce: function () {
console.log("Hi, I am " + this.name);
}
};
const student2 = { name: "Rahul" };
student1.introduce.call(student2);
// Hi, I am Rahul
No need to rewrite introduce for every object. Just borrow it. 💡
5. apply() — Same as call(), But Arguments Go in an Array
apply() is nearly identical to call(). The only difference is how you pass arguments: instead of individually, you pass them as an array.
Syntax
functionName.apply(thisArg, [arg1, arg2, ...])
Example
const person2 = { name: "Rahul" };
function greet(city, country) {
console.log(this.name + " lives in " + city + ", " + country);
}
greet.apply(person2, ["Delhi", "India"]);
// Rahul lives in Delhi, India
When is apply() useful?
When your arguments are already in an array — for example, when working with Math.max():
const numbers = [3, 7, 1, 9, 4];
console.log(Math.max.apply(null, numbers)); // 9
Note: In modern JavaScript, you can use the spread operator for this (
Math.max(...numbers)), butapply()is still valuable when you need to controlthisand have an array of arguments.
6. bind() — Set this and Use the Function Later
bind() does not call the function immediately. Instead, it returns a new function with this permanently bound to whatever you specify.
Syntax
const newFn = functionName.bind(thisArg, arg1, arg2, ...)
Example
const person3 = { name: "Mansi" };
function greet() {
console.log("Hello " + this.name);
}
const greetMansi = greet.bind(person3);
// Can be called any time, anywhere
greetMansi(); // Hello Mansi
Real-World Use Case: Event Listeners
bind() shines in situations where functions are passed as callbacks and lose their context.
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(this.tick.bind(this), 1000);
}
tick() {
this.seconds++;
console.log(this.seconds);
}
}
const t = new Timer();
t.start(); // 1, 2, 3 ...
Without .bind(this), this inside tick would be undefined (in strict mode) or the global object — and this.seconds would break.
7. call vs apply vs bind — The Final Comparison
| Feature | call() |
apply() |
bind() |
|---|---|---|---|
| Executes immediately | ✅ Yes | ✅ Yes | ❌ No |
| How arguments are passed | Individually | As an array | Individually |
| Returns a new function | ❌ No | ❌ No | ✅ Yes |
| Best for | One-time function borrowing | Array-based arguments | Callbacks & event handlers |
Memory trick 🧠
call → Call the function right now, comma-separated args
apply → Apply an array of args
bind → Bind for later use
8. Putting It All Together — A Full Mini Example
const student = {
name: "Jai"
};
function introduce(age, city) {
console.log(`Hi, I'm ${this.name}, ${age} years old, from ${city}.`);
}
// Using call()
introduce.call(student, 22, "Kanpur");
// Hi, I'm Jai, 22 years old, from Kanpur.
// Using apply()
introduce.apply(student, [22, "Kanpur"]);
// Hi, I'm Jai, 22 years old, from Kanpur.
// Using bind()
const boundIntroduce = introduce.bind(student);
boundIntroduce(22, "Kanpur");
// Hi, I'm Jai, 22 years old, from Kanpur.
Same result. Three different ways. Choose based on your use case.
9. Quick Gotchas to Watch Out For ⚠️
Arrow functions don't have their own this
const obj = {
name: "Jai",
greet: () => {
console.log(this.name); // undefined!
}
};
obj.greet();
Arrow functions inherit this from the surrounding lexical scope — they don't get their own. call(), apply(), and bind() cannot override this in arrow functions.
Losing this when extracting a method
const user = {
name: "Priya",
sayHi() {
console.log("Hi, " + this.name);
}
};
const fn = user.sayHi;
fn(); // Hi, undefined — context is lost!
const boundFn = user.sayHi.bind(user);
boundFn(); // Hi, Priya ✅
Summary
| Concept | One-liner |
|---|---|
this |
The object that called the function |
call() |
Execute now, set this, pass args one by one |
apply() |
Execute now, set this, pass args as array |
bind() |
Set this, return a new function for later |
The golden question to ask whenever this confuses you:
"Who is calling this function?"
Answer that, and 80% of this-related bugs become obvious.
Resources to Go Deeper
- MDN — this
- MDN — Function.prototype.call()
- MDN — Function.prototype.apply()
- MDN — Function.prototype.bind()
If this helped you, drop a ❤️ and share it with a fellow dev who's been fighting this for too long. And if you have questions or a use case I didn't cover, drop it in the comments — happy to help!
Top comments (0)