The Mystery of this, Solved

Sophomore Year, page 7 of 11. A three-minute read.

We've been bumping into this since Grade 8, and we promised we'd explain it properly in College. Here we are!

The tricky thing about this is that its value isn't decided when you write the function. It's decided when the function is called, based on how it's called. There are four rules.

Rule 1: Called as a method → this is the object before the dot

JavaScript
const dog = {
  name: "Bingo",
  bark() {
    return `${this.name} says woof!`;
  },
};

console.log(dog.bark());

It prints

Bingo says woof!

dog.bark() is called with dog before the dot, so inside bark, this is dog. This is by far the most common case.

Rule 2: Called with new → this is the brand-new object

JavaScript
const hero = new Character("Mia", 100);

Inside the constructor, this is the new object being built. You've seen this all through this chapter.

Rule 3: Called on its own → this is undefined

Here's where things go wrong. If you take a method out of its object and call it on its own, there's no object before the dot, so this is lost:

JavaScript
class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
    return this.count;
  }
}

const counter = new Counter();
console.log(counter.increment());

const loose = counter.increment;
loose();

It prints

1
Uncaught TypeError: Cannot read properties of undefined (reading 'count')

loose() has nothing before the dot, so this is undefined, and undefined.count crashes. (In some older, non-class code, this falls back to the global object instead of undefined, which is even more confusing. Classes always use the stricter, safer undefined behavior.)

This "lost this" problem happens most often when you pass a method as a callback, like button.addEventListener("click", counter.increment) or setTimeout(counter.increment, 1000). The method gets called on its own later, without its object.

Rule 4: Arrow functions don't have their own this

Arrow functions don't play by the rules above. They don't get their own this at all. Instead, they use the this from the surrounding code where they were written. That's why they make bad object methods (Grade 8), but they're perfect for callbacks inside methods:

JavaScript
class Team {
  constructor(name, members) {
    this.name = name;
    this.members = members;
  }

  introduce() {
    this.members.forEach((member) => {
      console.log(`${member} plays for ${this.name}`);
    });
  }
}

new Team("Red Rockets", ["Mia", "Omar"]).introduce();

It prints

Mia plays for Red Rockets
Omar plays for Red Rockets

The arrow function inside forEach borrows this from introduce, where this is the team. If we'd used a regular function (member) { ... } callback there, it would have been called on its own (Rule 3), and this.name would crash.

Fixing a lost this: bind

If you need to pass a method as a callback, you have two easy fixes. Wrap it in an arrow function, or bind it. bind creates a copy of the function with this permanently locked to the object you choose:

JavaScript
const fixed1 = () => counter.increment();
const fixed2 = counter.increment.bind(counter);

console.log(fixed1());
console.log(fixed2());

It prints

2
3
RecapThe four rules of this
  1. obj.method(): this is obj (the thing before the dot).
  2. new Thing(): this is the new object.
  3. fn() on its own: this is undefined (or the global object in old-style code).
  4. Arrow functions: no this of their own. They use the this of the code around them.

When in doubt, ask: "How is this function being called?" And when you pass a method as a callback, wrap it in an arrow function or use bind.

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning