Skip to Content
JavaScript Advanced: Code That Holds Up in Production
Advanced
0 likes
Courses
JavaScript Advanced: Code That Holds Up in Production

this, bind and Arrow Functions: Who Is Calling

The rules that decide what this is, why it goes missing in callbacks, and when to use bind, an arrow function or a class field. After this, "Cannot read properties of undefined" inside a method stops surprising you.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already know why setTimeout(obj.method, 0) loses this, skip to "Which fix to use".

this is not decided where a function is written. It's decided by how the function is called. That one rule explains almost every "Cannot read properties of undefined" error that happens inside a method. Arrow functions are the exception: they don't have their own this at all.

The rules, in order

For a normal function (function keyword or a method):

  1. Called as obj.method(): this is obj.
  2. Called with fn.call(x), fn.apply(x) or a function made with fn.bind(x): this is x.
  3. Called with new: this is the new object.
  4. Called as a plain fn(): this is undefined in strict mode, which includes every module and every class body.

Arrow functions skip all of that. They use the this of the place they were written.

"use strict";
const order = {
  id: 1042,
  describe() {
    return `Order ${this.id}`;
  },
};

console.log(order.describe());
const describe = order.describe;
try {
  console.log(describe());
} catch (error) {
  console.log(error.constructor.name + ": " + error.message);
}
Order 1042
TypeError: Cannot read properties of undefined (reading 'id')

Same function, two calls. The first has order. in front, so this is order. The second is a plain call, so this is undefined.

Where it goes missing

You rarely write const describe = order.describe on purpose. You do the same thing every time you pass a method as a callback:

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

const timer = new Timer();
const tick = timer.tick;
try {
  tick();
} catch (error) {
  console.log(error.message);
}
Cannot read properties of undefined (reading 'seconds')

setInterval(timer.tick, 1000), button.addEventListener("click", this.save) and items.forEach(this.addItem) all do this. The function is passed without the object, and whoever calls it later calls it plainly.

💡 Tip: In the browser, an event listener written with function gets the element as this, not your object. So the bug looks different there: no error, just the wrong object.

Three fixes

Arrow function at the call site. Wrap the call so the method is called with the object again:

setInterval(() => timer.tick(), 1000);

bind. Make a new function with this fixed:

setInterval(timer.tick.bind(timer), 1000);

Class field arrow. Write the method as an arrow function stored on each instance:

class Counter {
  count = 0;
  increment = () => {
    this.count++;
  };
}

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

The arrow function was created inside the instance, so its this is always that instance, however it's called.

Which fix to use

  • One callback in one place: an arrow function at the call site. It's the easiest to read.
  • A method passed around a lot, like a React-style event handler: a class field arrow.
  • You need to remove the listener later: keep a reference to the bound function, because removeEventListener needs the exact same function.
this.onResize = this.handleResize.bind(this);
window.addEventListener("resize", this.onResize);
// later
window.removeEventListener("resize", this.onResize);

Calling bind twice gives two different functions, so removeEventListener("resize", this.handleResize.bind(this)) removes nothing. The listener stays, and so does everything it refers to.

Arrow functions as methods

Arrow functions aren't a fix everywhere. As a method on an object literal, they break:

const shop = {
  name: "Xtensyon Store",
  label: () => `Shop: ${this?.name}`,
  title() {
    return `Shop: ${this.name}`;
  },
};
console.log(shop.label());
console.log(shop.title());
Shop: undefined
Shop: Xtensyon Store

The arrow function took this from the code around the object, not from the object. Use normal method syntax on objects, and arrows for callbacks inside them.

Common mistakes

  • Passing this.method to setTimeout, addEventListener or forEach.
  • Writing object methods as arrow functions.
  • Binding inside addEventListener and then trying to remove the listener with a new bind.
  • "Fixing" it with const self = this everywhere. It works, but an arrow function does the same thing with less code.

What happens if you skip this

You'll fix each "undefined" error by trial and error, adding bind in some places and arrows in others. The code works, but nobody can tell which version is right, and the listener that never gets removed keeps its whole object in memory.

In items.forEach(this.addItem), who actually calls addItem, and what does it pass as this?

About

The rules that decide what this is, why it goes missing in callbacks, and when to use bind, an arrow function or a class field. After this, "Cannot read properties of undefined" inside a method stops surprising you.

Duration
5 min
Last update
08/16/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.