Quick Answer

In JavaScript, this is determined by how a function is called, not where it is defined. There are four rules, checked in order: new binding sets this to the new object, explicit binding with call, apply or bind sets it to what you pass, method calls set it to the object before the dot, and everything else falls back to the global object or undefined in strict mode. Arrow functions ignore all four and inherit this from the surrounding scope, which is why they fix callback problems.

The Core Idea: Call Site, Not Definition Site

Most confusion about this comes from assuming it works like a variable — that a function written inside an object permanently belongs to it. It does not.

The value of this is decided at the moment the function is called, based on how it is called. The same function can have four different values of this depending on the call.

const user = {
  name: 'Riya',
  greet() { return `Hi, ${this.name}`; }
};

user.greet();              // "Hi, Riya"     — called on user

const fn = user.greet;
fn();                      // "Hi, undefined" — called on nothing

Nothing about greet changed. It was pulled out of the object and called plainly, so there is no object before the dot and this is no longer user.

Once you internalise "look at the call, not the definition", almost every confusing case becomes readable.

The Four Rules, In Priority Order

When a function runs, JavaScript checks these in order and the first match wins.

1. new binding. Called with new, this is the brand-new object being constructed.

function User(name) { this.name = name; }
const u = new User('Riya');   // this === the new object

2. Explicit binding. Called via call, apply or bind, this is whatever you passed.

greet.call(user);            // this === user
const bound = greet.bind(user);   // permanently bound

3. Implicit binding. Called as a method, this is the object immediately before the dot.

user.greet();                // this === user
app.data.user.greet();       // this === user, not app

Note that only the last object before the dot matters, which surprises people with nested objects.

4. Default binding. None of the above, so this is the global object — or undefined in strict mode, which modules and class bodies use automatically. That is why the error is usually "cannot read property of undefined" in modern code rather than a silent global.

The Callback Trap

This is where this actually bites in real code. You pass a method somewhere as a callback, and it loses its object.

class Timer {
  constructor() { this.seconds = 0; }

  start() {
    setInterval(function () {
      this.seconds++;          // TypeError: this is undefined
    }, 1000);
  }
}

The function inside setInterval is not called as a method of the timer. It is called by the timer mechanism, with no object before a dot — so rule four applies and this is not your instance.

The same happens with event handlers, map callbacks, and any function passed by reference:

button.addEventListener('click', user.greet);   // this === the button, not user
[1, 2].forEach(obj.process);                    // this === undefined

Three fixes exist. The modern one is an arrow function. The explicit one is bind. The old one, still worth recognising in legacy code, is capturing this in a variable:

const self = this;               // the 'var self = this' pattern
setInterval(function () { self.seconds++; }, 1000);

How Arrow Functions Change Everything

Arrow functions do not follow the four rules at all. They have no this of their own — they inherit it from the surrounding scope at the moment they are defined, and nothing can change it afterwards.

class Timer {
  constructor() { this.seconds = 0; }

  start() {
    setInterval(() => {
      this.seconds++;     // works: inherited from start(), which is the instance
    }, 1000);
  }
}

This is why arrows are the default choice for callbacks. But the same property makes them wrong for object methods:

const user = {
  name: 'Riya',
  greet: () => `Hi, ${this.name}`    // this is NOT user
};
user.greet();    // "Hi, undefined"

The arrow was defined in the surrounding scope, not inside a function call, so it inherited whatever this was out there — typically the module scope or the global object. Object literals do not create a this binding.

Arrows also cannot be used with new, and ignore call, apply and bind entirely — you can pass a different this and it will be silently ignored.

The rule of thumb: arrow functions for callbacks, regular functions for methods.

How to Debug a this Problem

When this is wrong, work through it mechanically rather than guessing.

  1. Find the call site, not the definition. Where is the function actually invoked?
  2. Is it an arrow function? If so, ignore the call entirely and look at the enclosing scope where it was written.
  3. Otherwise apply the four rules in order. Was it called with new? With call, apply or bind? With an object before the dot? If none, it is default binding.
  4. Check for a lost reference. Passing obj.method anywhere detaches it, because you are passing the function, not the pair of object-and-function.

Two practical notes. In React class components, this is why constructors were full of this.handleClick = this.handleClick.bind(this) — and why class property arrow functions replaced that pattern.

And in a DOM event handler written as a regular function, this is the element that received the event, which is genuinely useful — until you switch it to an arrow function and it silently becomes the enclosing scope instead. Use event.currentTarget if you want the element regardless of function type.

Frequently Asked Questions

Why is this undefined in my callback? Because the callback is invoked by something else with no object before a dot, so default binding applies — and in strict mode, which modules and classes use, that means undefined rather than the global object. Use an arrow function or bind the method.
What is the difference between call, apply and bind? call and apply invoke the function immediately, differing only in how arguments are passed — call takes them individually, apply takes an array. bind does not invoke it; it returns a new function with this permanently fixed.
Should I always use arrow functions? No. Use arrows for callbacks, where inheriting the surrounding this is what you want. Use regular functions for object methods and prototype methods, because an arrow defined in an object literal does not get the object as this.
Why does this work in a method but not when I pass the method? Because this depends on the call, not the function. Calling user.greet() gives implicit binding to user. Passing user.greet somewhere passes only the function, so when it is later called there is no object before the dot.
What is this at the top level of a module? In an ES module it is undefined, because modules are always strict mode. In a classic script in a browser it is the window object. This difference catches people out when converting scripts to modules.