DEV Community

Cover image for Mastering `this`, `call()`, `apply()`, and `bind()` in JavaScript — With Real Examples
Janmejai Singh
Janmejai Singh

Posted on

Mastering `this`, `call()`, `apply()`, and `bind()` in JavaScript — With Real Examples

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')
Enter fullscreen mode Exit fullscreen mode

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 this actually means (and why it's confusing)
  • How call(), apply(), and bind() 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:

this refers 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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

thiscar

Clean and predictable. The problem arises when you detach that method from its object.

const detached = car.showBrand;
detached(); // undefined — `this` lost its context!
Enter fullscreen mode Exit fullscreen mode

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, ...)
Enter fullscreen mode Exit fullscreen mode

Example

const person1 = { name: "Jai" };

function greet(city) {
  console.log("Hello " + this.name + " from " + city);
}

greet.call(person1, "Kanpur");
// Hello Jai from Kanpur
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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, ...])
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Note: In modern JavaScript, you can use the spread operator for this (Math.max(...numbers)), but apply() is still valuable when you need to control this and 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, ...)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 ✅
Enter fullscreen mode Exit fullscreen mode

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


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)