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.
-
Functions That Remember
-
JavaScript Closures in Real Code
-
this, bind and Arrow Functions: Who Is Calling
-
-
-
How JavaScript Actually Runs
-
The Event Loop: Microtasks, Macrotasks and Why setTimeout(0) Isn't Zero
-
Running Promises in Parallel: all, allSettled, race
-
Cancelling Work With AbortController
-
-
-
Data That Doesn't Fit in Memory
-
Iterators and Generators
-
Streams in Node.js: Reading Big Files a Piece at a Time
-
-
-
Catch Bugs Before You Run the Code
-
JSDoc Types and // @ts-check Without Switching to TypeScript
-
ESLint in a Real Project
-
-
-
Ship It, Then Make It Fast
-
Bundling With Vite and Publishing to npm
-
Finding Slow Code With the Performance Profiler
-
Memory Leaks: Event Listeners and Closures That Never Let Go
-
-
- Final Check: JavaScript Advanced
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):
- Called as
obj.method():thisisobj. - Called with
fn.call(x),fn.apply(x)or a function made withfn.bind(x):thisisx. - Called with
new:thisis the new object. - Called as a plain
fn():thisisundefinedin 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
removeEventListenerneeds 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.methodtosetTimeout,addEventListenerorforEach. - Writing object methods as arrow functions.
- Binding inside
addEventListenerand then trying to remove the listener with a newbind. - "Fixing" it with
const self = thiseverywhere. 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
Comments (0)
Sign in to commentNo comments yet.