Skip to main content

Command Palette

Search for a command to run...

`this` in JavaScript: The Complete Guide

Stop guessing what 'this' refers to. Master calling context, arrow functions, ES6 classes, 'call', 'apply', and 'bind' — with hands-on exercises.

Updated
`this` in JavaScript: The Complete Guide
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!

You know that feeling when your code looks completely fine — but it still breaks?

You wrote the function. You put it inside the object. You called it. And somehow, this.name comes back undefined, and you're left staring at the screen wondering what went wrong.

If that sounds familiar, you're probably dealing with JavaScript's most misunderstood keyword: this.

  • Have you ever called a method on an object and gotten undefined for no obvious reason?

  • Have you ever passed a function as a callback and suddenly lost access to your object's data?

  • Have you ever fixed a this bug by accident — and had no idea why it worked?

  • Have you ever wondered why the same function behaves differently depending on where you call it?

The problem isn't that you're bad at JavaScript. The problem is that this breaks every expectation you bring from other languages — and most tutorials never explain the one rule that makes it all predictable.

If you've ever felt confused by this, this article is exactly for you.


✅ What You'll Learn

  • What this actually is — and the one mental model that makes it click

  • How this behaves in global scope, inside objects, and inside functions

  • Why the same function can produce different this values depending on how it's called

  • How arrow functions changed everything about this in modern JavaScript

  • How to take full control of this using call(), apply(), and bind()

  • How this works inside classes — essential for real-world applications

No deep theory. No execution context internals. Just clear rules and real examples.


The One Rule That Governs Everything

Before diving into examples, here's the single insight that makes this predictable:

this is determined by who calls the function — not where the function is written.

Read that again. It's not about where the function lives. It's not about where it was defined. It's about the caller.

Caller → Function → this = Caller

Every time you're confused about this, just ask one question:

👉 "Who is calling this function right now?"

The answer to that question is this. Keep this rule in your head as you read through every example below — it will always hold.


this in the Global Context

The simplest case. No objects, no functions — just this sitting at the top level.

In a Browser

console.log(this); // window

The global object in the browser is window, so this points to it.

In Node.js

console.log(this); // {}

In Node.js, the top-level this is module.exports — which starts as an empty object.

In Strict Mode

"use strict";

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

show();

Strict mode is important. Without an explicit caller, JavaScript no longer defaults this to the global object — it becomes undefined instead. This is actually safer, because it forces you to be intentional.

Context this value
Browser (global) window
Node.js (global) {} (module.exports)
Strict mode function undefined

this Inside Objects

This is where this becomes genuinely useful. Let's use a user object that we'll build on throughout this article.

const user = {
  name: "Aman",
  greet: function () {
    console.log(`Hello, I'm ${this.name}`);
  }
};

user.greet(); // Hello, I'm Aman

Apply the rule: who calls greet()? The object user does. So this = user, and this.name gives us "Aman".

user → greet()
this = user ✅

Now let's see what happens when we detach the method:

const fn = user.greet;
fn(); // Hello, I'm undefined

Same function. Different result. Why?

fn()  ← no object calling it
this = window (or undefined in strict mode) ❌

When you pull a method off an object and call it standalone, you lose the caller. The function didn't change — the calling context did.

Key lesson: Detaching a method from its object strips away its this context.


🏋️ Exercise 1

Predict the output of this code before running it:

const counter = {
  count: 10,
  increment: function () {
    console.log(this.count + 1);
  }
};

const standalone = counter.increment;

counter.increment(); // What prints here?
standalone();        // What about here?

See Answer

  • counter.increment()11 (caller is counter, this.count is 10)

  • standalone()NaN (caller is window, window.count is undefined, undefined + 1 is NaN)


How Calling Context Changes this

This is the core of everything. The same function can produce completely different this values — all depending on how it's invoked.

function greet() {
  console.log(`Hi, I'm ${this.name}`);
}

const user1 = { name: "Aman", greet };
const user2 = { name: "Riya", greet };

user1.greet(); // Hi, I'm Aman
user2.greet(); // Hi, I'm Riya
greet();       // Hi, I'm undefined (or error in strict mode)

Three calls. Three different this values. Same function.

user1 → greet()  →  this = user1
user2 → greet()  →  this = user2
greet()          →  this = window

This is this in its purest form — dynamic, runtime-determined, and entirely dependent on the caller.

The setTimeout Trap

One of the most common real-world bugs:

const user = {
  name: "Aman",
  greet: function () {
    console.log(`Hello, I'm ${this.name}`);
  }
};

setTimeout(user.greet, 1000); // Hello, I'm undefined

Why? When you pass user.greet to setTimeout, you're handing over the function reference — detached from user. After one second, setTimeout calls the function, and setTimeout is the caller now, not user. The this context is lost.

This exact bug appears constantly in event handling, API callbacks, and timers. Keep it in mind.


Arrow Functions: A Different Kind of this

Modern JavaScript introduced arrow functions, and they fundamentally changed how this works. This is where many developers get tripped up.

Arrow functions do not have their own this. They inherit this from the surrounding scope where they were defined.

Compare these two:

const user = {
  name: "Aman",

  greetRegular: function () {
    console.log(this.name); // "Aman" — this = user
  },

  greetArrow: () => {
    console.log(this.name); // undefined — this = window (outer scope)
  }
};

user.greetRegular(); // Aman ✅
user.greetArrow();   // undefined ❌

The arrow function is defined inside the object literal, but object literals don't create a new this scope. So the arrow function looks outward to the enclosing scope — which is the global scope — and this becomes window.

When Arrow Functions Are Your Best Friend

Arrow functions shine when you're working inside a regular function and want to preserve this:

const user = {
  name: "Aman",
  friends: ["Riya", "Karan"],

  listFriends: function () {
    // 'this' here is user ✅
    this.friends.forEach((friend) => {
      // Arrow function inherits 'this' from listFriends
      console.log(`\({this.name} knows \){friend}`);
    });
  }
};

user.listFriends();
// Aman knows Riya
// Aman knows Karan

If you used a regular function inside forEach, this would break. The arrow function borrows this from listFriends — which is correctly bound to user.

Arrow Function this Rule

Arrow function → looks outward → inherits this from enclosing scope
Function Type this determined by
Regular function Who calls it (runtime)
Arrow function Where it's defined (lexical)

🏋️ Exercise 2

What will this print?

const timer = {
  seconds: 0,
  start: function () {
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
};

timer.start();

See Answer

It prints 1, 2, 3, ... every second, incrementing correctly.

Because setInterval receives an arrow function, which inherits this from start(). And start() is called by timer, so this = timer. The arrow function correctly accesses timer.seconds.

If you replaced the arrow function with a regular function, this would be window and window.seconds would be NaN.


Taking Control: call(), apply(), and bind()

So far, this has been decided by the caller automatically. But JavaScript gives you three tools to explicitly set this yourself.

call() — Call with a specific this, arguments one by one

function introduce(role, company) {
  console.log(`I'm \({this.name}, a \){role} at ${company}.`);
}

const user = { name: "Aman" };

introduce.call(user, "developer", "OpenAI");
// I'm Aman, a developer at OpenAI.

call() immediately invokes the function with this set to user.

apply() — Same as call(), but arguments as an array

introduce.apply(user, ["developer", "OpenAI"]);
// I'm Aman, a developer at OpenAI.

Useful when your arguments are already in an array.

bind() — Returns a new function with this permanently locked

const boundIntroduce = introduce.bind(user);

boundIntroduce("developer", "OpenAI");
// I'm Aman, a developer at OpenAI.

bind() doesn't call the function immediately. It creates a new function where this is permanently set to user — no matter how or where that new function is called later.

This is the fix for the setTimeout bug we saw earlier:

setTimeout(user.greet.bind(user), 1000);
// Hello, I'm Aman ✅

Quick Reference

Method Calls immediately? Arguments
call(obj, a, b) ✅ Yes Comma-separated
apply(obj, [a, b]) ✅ Yes Array
bind(obj) ❌ No (returns new fn) Comma-separated later

🏋️ Exercise 3

Fix this broken code using bind():

const wallet = {
  balance: 500,
  showBalance: function () {
    console.log(`Balance: $${this.balance}`);
  }
};

const show = wallet.showBalance;
show(); // Balance: $undefined ❌

// Your fix here — make it print "Balance: $500"

See Answer

const show = wallet.showBalance.bind(wallet);
show(); // Balance: $500 ✅

this Inside Classes

Classes in JavaScript are built on top of the same this system — which means the same rules apply. Understanding this protects you from a very common class-related bug.

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

const user = new User("Aman");
user.greet(); // Hello, I'm Aman ✅

When you call user.greet(), user is the caller, so this = user. Clean and predictable.

The Class Method Detachment Bug

But the same trap from earlier hits classes too:

const greetFn = user.greet;
greetFn(); // Hello, I'm undefined ❌

And it's especially dangerous with event listeners:

document.querySelector("button").addEventListener("click", user.greet);
// Prints: Hello, I'm undefined ❌
// Because the button is now the caller, not user

The Fix: Arrow Class Fields

Modern JavaScript lets you define methods as arrow function properties, which permanently binds this at the time the class instance is created:

class User {
  constructor(name) {
    this.name = name;
  }

  greet = () => {
    console.log(`Hello, I'm ${this.name}`);
  }
}

const user = new User("Aman");
document.querySelector("button").addEventListener("click", user.greet);
// Hello, I'm Aman ✅ — works correctly now

This pattern is widely used in React and other component-based frameworks.


The Complete Mental Map

Here's every scenario in one place:

How the function is called this equals
obj.fn() — called on an object That object
fn() — called standalone window (or undefined in strict mode)
fn.call(obj) / fn.apply(obj) Whatever you explicitly pass in
Arrow function () => {} this inherited from the enclosing scope
new MyClass() The newly created instance
DOM event listener The element that triggered the event

What to Learn Next

Mastering this opens the door to several powerful JavaScript concepts. Here's a logical path forward:

  1. Prototypes and Prototype Chainthis is the foundation of how JavaScript's prototype system works. Understanding it will make inheritance click.

  2. ES6 Classes in Depth — Now that you understand this in classes, explore inheritance, super(), and private fields.

  3. Closuresthis and closures are often confused. Learning both side-by-side will sharpen your understanding of scope and context.

  4. The Event Loop — Understand why setTimeout and async callbacks lose this context, and how the event loop drives JavaScript execution.


💬 Got Questions?

Drop a comment below! I'd love to hear about any this-related bug that stumped you, or help clarify any section that didn't fully click.

Here are topics coming up in future articles:

  • Closures Explained Simply: Why functions "remember" variables even after they've finished running — and how to use that power intentionally.

  • Prototypes Without the Confusion: A visual walkthrough of how JavaScript's prototype chain actually works under the hood.

  • async/await From Zero: Promises are great, but async/await makes asynchronous code feel synchronous — here's how to write it confidently.

  • call(), apply(), bind() Deep Dive: Real-world use cases, patterns, and gotchas for all three methods — with exercises.


Found this helpful? Share it with someone who's been bitten by a this bug lately — you might just save them an hour of debugging.

Zero to Full Stack Developer: From Basics to Production

Part 29 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

OOP in JavaScript: A Beginner's Guide

Master classes, objects, constructors, and encapsulation in JavaScript — with hands-on exercises and real-world examples.