Skip to main content

Command Palette

Search for a command to run...

JavaScript's this Keyword Explained

How to control function context with call(), apply(), and bind() — with hands-on examples and common mistakes

Updated
JavaScript's this Keyword Explained
M

👋 Hey, I'm Mohd Kaif – a student documenting my journey through code. I write about what I'm learning in real-time – the wins, the struggles, and the "aha!" moments. From JavaScript and React to backend systems with Node.js, databases, DevOps, TypeScript, and AI integrations. This blog is my public learning journal: honest, evolving, and always exploring. If you're curious about any of these topics, let's learn and build together!

I used to think this in JavaScript was broken.

Not "tricky." Not "nuanced." Broken. Like the language designers had a meeting, decided to make one keyword mean six different things depending on the weather, and called it a feature.

Sound familiar? Maybe you've been there too:

  • You write a method inside an object, call it somewhere else, and this is suddenly undefined

  • You copy a function from one object to another and it stops working for no obvious reason

  • You've seen call(), apply(), and bind() in documentation or Stack Overflow answers — but you skip them because they look intimidating

  • You've fixed a this bug by accident and weren't sure why your fix worked

Here's the thing: the problem isn't you. this is genuinely confusing if nobody explains the single rule that governs it. Most tutorials dump five rules on you at once, and your brain quits.

This article teaches you that one rule — and then shows you three powerful tools that put you in full control of this.


✅ What You'll Learn

  • Why this behaves differently depending on how you call a function (not where you write it)

  • How call() lets you invoke any function with a custom this on the spot

  • How apply() does the same thing — with one key difference for handling arguments

  • How bind() creates a permanently locked version of a function you can reuse anywhere

  • When to use each one, with real-world scenarios you'll actually encounter

  • What mistakes to watch out for — and how to recognize them before they bite you

No deep theory required. If you know what a function and an object are in JavaScript, you're ready.


The One Rule That Explains Everything

Before we look at any code, here's the mental model that unlocks all of this:

this refers to whoever is calling the function — not where the function was written.

Read that again. It's worth it.

this is not set when you define a function. It's set when you call it. The caller determines the context. That's it. Everything else — call, apply, bind, arrow functions — is just a consequence of this one idea.


this in a Plain Function Call

Let's start at the simplest possible case.

function greet() {
  console.log(this);
}

greet();

Who's calling greet() here? Nobody specific — you just called it loose, on its own. So this has no meaningful owner.

  • In non-strict mode (browser): this defaults to the global window object

  • In strict mode: this is undefined

"use strict";

function greet() {
  console.log(this); // undefined
}

greet();

The takeaway: A function called by itself has no caller, so this is either the global object or undefined. Not very useful — and often the source of bugs.


this Inside an Object Method

Now things get more interesting.

const user = {
  name: "Rahul",
  greet: function () {
    console.log("Hello, I am " + this.name);
  },
};

user.greet(); // Hello, I am Rahul

Why does this work? Because user is the caller. You wrote user.greet() — so JavaScript sets this = user before running the function.

Think of it like this:

user → calls greet() → this = user ✅

Simple. But here's where developers get tripped up — what happens when you detach the function?

const fn = user.greet;
fn(); // Hello, I am undefined ❌

You took the same function and called it without an owner. Now this is lost. The function didn't change — the caller did.

This is the most common this bug in JavaScript. Memorize this pattern.


🛠️ Quick Challenge #1

Before moving on, try this in your browser console or a code sandbox:

const car = {
  brand: "Toyota",
  describe: function () {
    console.log("This is a " + this.brand);
  },
};

const detached = car.describe;

car.describe(); // What does this print?
detached();     // What does this print? Why?

Predict the output before running it. If you got it right, the mental model is clicking. If not — re-read the section above and try again.


Introducing call(), apply(), and bind()

So you have a function. You want to control what this is when it runs. That's exactly what these three methods do.

Think of them as a steering wheel for this. Instead of letting JavaScript guess who the caller is, you tell it explicitly.

Let's walk through each one.


call() — Invoke Immediately, Pass Arguments One by One

call() runs a function right away, with a this value you choose.

Syntax:

functionName.call(thisArg, arg1, arg2, ...);

Example:

const person1 = { name: "Rahul" };
const person2 = { name: "Aman" };

function greet(city) {
  console.log(`Hi, I am \({this.name} from \){city}`);
}

greet.call(person1, "Delhi");  // Hi, I am Rahul from Delhi
greet.call(person2, "Mumbai"); // Hi, I am Aman from Mumbai

The same function, two different callers, two different results. No duplication. No separate methods on each object.

Key idea: call() executes immediately. Arguments go in one at a time after thisArg.


Method Borrowing — The Most Practical Use of call()

Here's a real pattern you'll use in actual codebases. Imagine person1 has a method, and you want person2 to use it — without copying the function.

const person1 = {
  name: "Rahul",
  greet: function () {
    console.log(`Hello, I am ${this.name}`);
  },
};

const person2 = {
  name: "Aman",
};

person1.greet.call(person2); // Hello, I am Aman

You borrowed person1's method and ran it as if it belonged to person2. This is called method borrowing, and it's one of the most elegant patterns in JavaScript.


apply() — Same as call(), But Arguments Go in an Array

apply() is nearly identical to call(). The only difference: instead of listing arguments one by one, you pass them as a single array.

Syntax:

functionName.apply(thisArg, [arg1, arg2, ...]);

Example:

const person = { name: "Rahul" };

function greet(city, country) {
  console.log(`Hi, I am \({this.name} from \){city}, ${country}`);
}

greet.apply(person, ["Delhi", "India"]);
// Hi, I am Rahul from Delhi, India

When is this useful? When your arguments are already in an array — from an API response, user input, or another function — you don't have to unpack them manually.

const args = ["Delhi", "India"];
greet.apply(person, args); // Works perfectly ✅

🛠️ Quick Challenge #2

You have this setup:

const product = { name: "Laptop" };

function describe(price, currency) {
  console.log(`\({this.name} costs \){currency}${price}`);
}

const details = [999, "$"];

Call describe using:

  1. call() — passing the arguments individually

  2. apply() — passing the details array directly

Both should print: Laptop costs $999


bind() — Create a New Function with this Locked In

bind() is where things get really powerful — and where most people get confused at first.

Unlike call() and apply(), bind() does not run the function. It returns a new function with this permanently fixed to whatever you pass in.

Syntax:

const newFunction = functionName.bind(thisArg, arg1, arg2, ...);

Example:

const person = { name: "Rahul" };

function greet(city) {
  console.log(`Hi, I am \({this.name} from \){city}`);
}

const boundGreet = greet.bind(person, "Delhi");

// Nothing has run yet. boundGreet is just a new function.

boundGreet(); // Hi, I am Rahul from Delhi ✅

You can also call it later, store it, pass it around — and this will always be person, no matter where it gets called from.

Why does this matter? Two huge real-world scenarios:

1. Event Handlers

const user = {
  name: "Rahul",
  handleClick: function () {
    console.log(`${this.name} clicked the button`);
  },
};

// Without bind — this = the button DOM element ❌
button.addEventListener("click", user.handleClick);

// With bind — this = user ✅
button.addEventListener("click", user.handleClick.bind(user));

2. Callbacks and Timers

const timer = {
  message: "Time's up!",
  start: function () {
    setTimeout(function () {
      console.log(this.message); // undefined ❌ — 'this' is lost
    }, 1000);

    setTimeout(function () {
      console.log(this.message); // "Time's up!" ✅
    }.bind(this), 1000);
  },
};

timer.start();

🛠️ Quick Challenge #3

Take the car object from Challenge #1 and do all three:

const car = {
  brand: "Toyota",
  describe: function (color) {
    console.log(`This is a \({color} \){this.brand}`);
  },
};

const newCar = { brand: "Honda" };
  1. Use call() to run describe with newCar as this, passing "red" as the color

  2. Use apply() to do the same, but pass the color inside an array

  3. Use bind() to create a new function hondaDescribe that always uses newCar as this, then call it with "blue"


call vs apply vs bind — The Full Comparison

call() apply() bind()
Executes immediately? ✅ Yes ✅ Yes ❌ No
Returns Function result Function result New function
How arguments are passed Individually: (ctx, a, b) As array: (ctx, [a, b]) Individually: (ctx, a, b)
Best used for One-off calls with custom this When args are already in an array Callbacks, event handlers, reusable functions

A simple way to remember the difference between call and apply:

Call → Comma-separated arguments Apply → Array arguments


The Mistakes That Will Get You (And How to Avoid Them)

Mistake 1: Losing this When Detaching a Method

const obj = {
  name: "Rahul",
  greet: function () {
    console.log(this.name);
  },
};

const fn = obj.greet;
fn(); // undefined ❌

Why it happens: fn() is called without an owner, so JavaScript has no caller to assign to this. The function is the same — the calling context changed.

Fix it:

fn.call(obj);       // immediate fix
const safe = obj.greet.bind(obj); // permanent fix
safe();

Mistake 2: Expecting bind() to Execute

greet.bind(person); // ❌ Nothing happens — you forgot to call it!

const bound = greet.bind(person);
bound(); // ✅ Now it runs

bind() is a factory — it makes a function. You still have to invoke what it gives you.


Mistake 3: Mixing Up Argument Format

greet.call(person, ["Delhi", "India"]);  // ❌ args become a nested array
greet.apply(person, "Delhi");            // ❌ apply expects an array

Fix:

greet.call(person, "Delhi", "India");    // ✅ individual args
greet.apply(person, ["Delhi", "India"]); // ✅ array of args

What to Learn Next

Understanding this is a milestone — and it connects directly to several powerful JavaScript concepts:

  • Arrow Functions and this: Arrow functions don't have their own this at all — they inherit it from the surrounding scope. This makes them behave very differently from regular functions, and it's a crucial distinction in modern JavaScript.

  • class syntax and methods: JavaScript classes rely on this throughout. Now that you understand how this works, class methods and constructors will make much more sense.

  • Closures: Closures and this interact in subtle ways — especially inside callbacks and timers. Understanding one deepens your understanding of the other.

  • Prototype chain: Method borrowing with call() is closely related to how JavaScript's prototype system works under the hood. Once you're comfortable with this, prototypes are a natural next step.


💬 Got Questions?

Drop a comment below — I'd love to hear about a this-related bug you've encountered, or help you work through something that's still fuzzy.

Here are topics coming up in future articles:

  • Arrow Functions and this: Why arrows don't have their own this, and when that saves you vs. trips you up

  • JavaScript Prototypes Explained: How objects inherit from each other, and why understanding this makes it easier

  • Closures in Plain English: What closures actually are, why they exist, and the patterns you'll see in real codebases

  • Async/Await Deep Dive: Writing clean asynchronous code and the common pitfalls that break it


Found this helpful? Share it with someone who's been burned by a this bug — you might save them hours of debugging. And if you want more articles like this, follow along so you don't miss the next one.

Zero to Full Stack Developer: From Basics to Production

Part 31 of 50

Complete full-stack web development series from zero to production. Learn HTML, CSS, JavaScript, TypeScript, React, Next.js, Node.js, databases, Docker, AWS, and AI integration. Build real-world projects step-by-step.

Up next

JavaScript Objects: A Complete Guide

Master key-value pairs, property access, loops, and real-world data modeling — with hands-on challenges at every step.