AniUI Academy

The this Keyword

The value of this is decided by how a function is called, not where it is written — the four call patterns, arrow functions, and the lost-this callback bug.

12 min read

You pass a method to setTimeout and it throws Cannot read properties of undefined. The method did not change. What changed is how it got called. That sentence is nearly the whole lesson: this is decided at the call site.

this is a hidden parameter

Every normal function receives an extra argument you never declare. JavaScript fills it in based on how the call was written, then makes it available as this. Nothing about where the function was defined affects it.

So the useful question is never "what is this here?" It is "how was this function called?" There are four answers.

Call pattern one: a plain call

function whoAmI() {
  return this;
}
 
whoAmI(); // undefined

In a module or in strict mode, a plain call sets this to undefined. In an old non-strict script it becomes the global object instead, which is worse — it hides the mistake and lets you write to globals by accident. All module code is strict, so treat undefined as the rule.

Call pattern two: a method call

If there is a dot, this is whatever is to the left of it.

const cart = {
  currency: "INR",
  items: [{ price: 199 }, { price: 499 }],
 
  total() {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  },
};
 
cart.total(); // 698 — this is cart

The object does not own the function in any deep sense. It just happened to be before the dot at call time. Move the same function somewhere else and this moves with the new call site.

const other = { items: [{ price: 50 }] };
other.total = cart.total;
 
other.total(); // 50 — same function, different this

Call pattern three: new

new creates a fresh empty object, sets this to it, runs the function, and returns that object.

function Lesson(title, minutes) {
  this.title = title;
  this.minutes = minutes;
}
 
const lesson = new Lesson("Closures", 13);
console.log(lesson.title); // "Closures"

Forget the new and it degrades into pattern one: this is undefined and the assignment throws. This is one reason class exists — calling a class without new is an error rather than a silent mess.

Call pattern four: call, apply and bind

You can set this yourself.

function describe(prefix) {
  return prefix + " " + this.title;
}
 
const lesson = { title: "Closures" };
 
describe.call(lesson, "Lesson:");     // "Lesson: Closures"
describe.apply(lesson, ["Lesson:"]);  // same, arguments as an array
 
const describeLesson = describe.bind(lesson);
describeLesson("Lesson:");            // "Lesson: Closures"

call and apply invoke immediately and differ only in how arguments are passed. bind invokes nothing — it returns a new function with this locked in. A bound function cannot be rebound, so bind once and keep the result.

Arrow functions have no this

An arrow does not get its own this. It uses the this of the code around it, decided when it was written rather than when it is called. Nothing you do at the call site changes that — not call, not bind.

That makes arrows correct for callbacks:

const cart = {
  items: [{ price: 199 }, { price: 499 }],
  currency: "INR",
 
  labels() {
    return this.items.map((item) => `${item.price} ${this.currency}`);
  },
};
 
cart.labels(); // ["199 INR", "499 INR"]

The arrow inside map sees the labels method's this, so this.currency works. Write that callback as function (item) { ... } instead and this becomes undefined inside it.

And it makes arrows wrong for methods:

const broken = {
  currency: "INR",
  label: () => this.currency, // this comes from outside the object
};
 
broken.label(); // undefined, or a TypeError in a module

An object literal does not create a scope, so the arrow reaches past it to whatever this the surrounding file has. Use shorthand methods for methods and arrows for callbacks.

A plain function nested inside a method

This one catches people who have already learned the rules, because the nested function looks like it is inside the method and therefore inside the object.

const cart = {
  items: [{ price: 199 }, { price: 499 }],
  currency: "INR",
 
  report() {
    this.items.forEach(function (item) {
      console.log(item.price, this.currency); // TypeError
    });
  },
};

forEach calls that callback plainly, so this is undefined inside it. The method's this is irrelevant — the callback got its own.

Before arrows, people worked around it by copying this into a variable, and you will still see this in older code:

report() {
  const self = this;
  this.items.forEach(function (item) {
    console.log(item.price, self.currency);
  });
}

An arrow does the same job without the extra name. Most array methods also accept a second thisArg argument — forEach(callback, this) — but it only works for function callbacks and reads worse than an arrow. Use the arrow.

The bug you will actually hit

Passing a method somewhere else strips the dot, and the dot was the only thing supplying this.

class Player {
  constructor() {
    this.playing = false;
  }
 
  toggle() {
    this.playing = !this.playing;
  }
}
 
const player = new Player();
 
button.addEventListener("click", player.toggle);
// TypeError: Cannot set properties of undefined

addEventListener stores the function and later calls it plainly. There are three fixes, and they are all common in real code.

// 1. bind at the point of use
button.addEventListener("click", player.toggle.bind(player));
 
// 2. wrap in an arrow, which keeps the dot
button.addEventListener("click", () => player.toggle());
 
// 3. define it as a class field, bound once per instance
class Player {
  playing = false;
  toggle = () => {
    this.playing = !this.playing;
  };
}

The arrow wrapper is usually clearest. The class field is convenient but puts a separate function on every instance instead of one on the prototype, which matters only when you create very many objects.

Try it yourself

The first line matters. Without it the detached call gets the global object instead of undefined, and the bug hides behind a NaN rather than throwing.

Try it yourself
Loading playground...

One extra case: event handlers

When the DOM calls a handler you registered with addEventListener, it sets this to the element the listener is attached to.

button.addEventListener("click", function () {
  console.log(this.textContent); // the button's text
});
 
button.addEventListener("click", () => {
  console.log(this); // NOT the button — the arrow ignores the DOM
});

event.currentTarget gives you the same element without relying on this, so in modern code you can use arrows everywhere and read the element from the event.

setTimeout behaves differently again. It calls your callback as a plain function, but in a browser the timer sets this to the global object rather than undefined, so a mistake there fails silently instead of throwing.

setTimeout(function () {
  console.log(this); // Window in a browser, a Timeout object in Node
}, 0);

The lesson is not to memorise each host's behaviour. It is to stop relying on this in any callback and use an arrow, which simply keeps the this you already had.

What to remember

  • this depends on the call, not the definition. Read the call site first.
  • Plain call: undefined. Dot call: the object before the dot. new: the new object. call/apply/bind: whatever you passed.
  • Arrows have no this — right for callbacks, wrong for object methods and constructors.
  • A method handed off as a callback loses this; fix it with bind, an arrow wrapper, or a class field.

Check yourself

4 questions · pass 3/4 to unlock Objects, Classes and Prototypes

up to 50
  1. 1.A module defines an object with an inc() method that does this.count += 1. You do const inc = counter.inc and call inc(). What happens?

  2. 2.What decides the value of this inside a normal function?

  3. 3.Where is an arrow function the right choice?

  4. 4.What does handleClick.bind(component) do?

4 left to answer