this Keyword & Execution Context

intermediate this bind call apply arrow-functions execution-context

this is one of the most confusing parts of JavaScript, and it comes up in almost every OOP interview. The key thing to understand: this is NOT fixed when we write the code — it’s determined when the function is called. The same function can have different this values depending on how we call it.

The Rules of this

There are a few simple rules. Once we know them, this stops being mysterious.

How is this determined?
1. Arrow function?
Uses this from surrounding scope (lexical). Can't be changed.
↓ no
2. Called with new?
this = the newly created object.
↓ no
3. Called with call/apply/bind?
this = whatever we explicitly pass in.
↓ no
4. Called as obj.method()?
this = the object before the dot.
↓ no
5. Plain function call
this = undefined (strict mode) or window (sloppy mode).

this in Global Context

In the global scope (outside any function), this refers to the global object — window in browsers, globalThis everywhere.

console.log(this === window); // true (in a browser)

this in Regular Functions

For a regular function, this depends on how it’s called, not where it’s written.

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

showThis();          // window (sloppy mode) or undefined (strict mode)

const obj = { name: "Manish", showThis };
obj.showThis();      // { name: "Manish", showThis: f } — the object before the dot

The same function, two different this values. That’s the core idea.

this in Object Methods

When we call a method on an object, this is the object that owns the method — the thing before the dot.

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

user.greet(); // Hi, I'm Manish — this = user

this in Constructors

When we use new, JavaScript creates a fresh empty object and sets this to point to it.

function Person(name) {
  this.name = name; // this = the new object being created
}

const p = new Person("Manish");
console.log(p.name); // "Manish"

Arrow Functions & Lexical this

Arrow functions don’t get their own this. They capture this from wherever they were defined. This is called lexical this and it’s one of the biggest reasons arrow functions exist.

const team = {
  name: "Avengers",
  members: ["Tony", "Steve"],
  printMembers() {
    this.members.forEach((member) => {
      console.log(`${member} is in ${this.name}`); // this = team (from printMembers)
    });
  }
};

team.printMembers(); // Tony is in Avengers, Steve is in Avengers

If we used a regular function inside forEach, this would be undefined (strict mode) instead of team. Arrow functions save us here.

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

These let us explicitly set this:

  • call(thisArg, arg1, arg2, ...) — calls the function immediately with the given this
  • apply(thisArg, [argsArray]) — same as call, but arguments are an array
  • bind(thisArg) — returns a new function with this permanently set
function greet(greeting) {
  console.log(`${greeting}, I'm ${this.name}`);
}

const user = { name: "Manish" };

greet.call(user, "Hey");     // Hey, I'm Manish
greet.apply(user, ["Hey"]);  // Hey, I'm Manish

const boundGreet = greet.bind(user);
boundGreet("Hey");           // Hey, I'm Manish

The only difference between call and apply is how we pass arguments — individually vs as an array.

this in Event Handlers

In DOM event handlers, this is the element that the listener is attached to.

button.addEventListener("click", function () {
  console.log(this); // the button element
  this.style.color = "red";
});

// But with arrow function:
button.addEventListener("click", () => {
  console.log(this); // NOT the button — it's the outer this (probably window)
});

This is one case where we actually want a regular function, not an arrow function.

The Classic Pitfall: Losing this

The most common bug with this happens when we pass a method as a callback. The method gets detached from its object and this becomes undefined.

class Timer {
  constructor() {
    this.seconds = 0;
  }
  start() {
    // BUG: regular function loses this
    // setInterval(function() { this.seconds++; }, 1000); // this = undefined!

    // FIX 1: arrow function (inherits this from start())
    setInterval(() => { this.seconds++; }, 1000);

    // FIX 2: bind
    // setInterval(function() { this.seconds++; }.bind(this), 1000);
  }
}

Whenever we see this inside a callback and it’s not working, the first thing to check is whether we’re using an arrow function or need to .bind(this).

this in Classes

Class methods behave like object methods — this is the instance. But the same “losing this” problem applies if we pass a method as a callback.

class Logger {
  prefix = "[LOG]";
  log(msg) {
    console.log(`${this.prefix} ${msg}`);
  }
}

const logger = new Logger();
logger.log("hello");           // [LOG] hello

const fn = logger.log;
// fn("hello");                // TypeError: Cannot read properties of undefined

const boundFn = logger.log.bind(logger);
boundFn("hello");              // [LOG] hello

In simple language, this in JavaScript isn’t about where we write the function — it’s about how we call it. Arrow functions are the exception because they lock in this from their surrounding scope. When in doubt, check the call site or use .bind().

References