this: decided at the call site
GoByte Skills #18: this is decided at the call site, not where the function is written. One function, three calls, three answers, and why arrows opt out.
Zoe is one of GoByte's characters. This post was drafted by AI agents in Zoe's voice, then fact checked, run and edited by the GoByte team.
In a regular function, this is set by how it is called, not where it is written, and arrow functions opt out. this is an invisible extra argument, and the call site fills it in.
Transcript
The same function is called three ways and a pointer swings its this to a new receiver each time, cat, then dog, then nothing, because the call site decides; then an arrow inside box.run points at box, because arrows use the outer this.
"use strict";
function who() {
return this ? this.name : "no this";
}
const cat = { name: "cat", who };
const dog = { name: "dog" };
console.log(cat.who()); // method call
console.log(who.call(dog)); // explicit
const lost = cat.who;
console.log(lost()); // plain call
const box = {
name: "box",
run() {
return [1].map(() => this.name);
},
};
console.log(box.run());
Node 20 prints:
cat
dog
no this
[ 'box' ]
The mechanism#
One function, three receivers. In cat.who() the call is made on a property reference, so the object left of the dot becomes this. who.call(dog) passes the receiver explicitly. lost() is a plain call with no receiver, so in strict mode this is undefined. In sloppy mode it would be globalThis, which is worse: the bug stays quiet. Copying cat.who into lost copied the function, not the dot.
The fourth rule is new, which makes this a fresh object. bind returns a function whose this is fixed: calling it with .call(dog) still gets cat. Only new overrides a bound this.
Arrows opt out#
An arrow function has no this of its own. It reads the one from the scope it was written in, like any other variable. That is why the arrow inside box.run sees box. Same reason an arrow cannot be rebound: call, apply and bind have no this to set.
The boundary#
Passing a method as a callback hands over the function and drops the receiver. The callee decides what this becomes: [1].forEach(fn) passes undefined in strict mode, and Node's setTimeout passes its own Timeout object, so this.name is undefined with no error. Classes are strict by default, which is why an unbound React class handler throws instead.
Fixes, in order of preference: write the callback as an arrow (() => cat.who()), or bind once in the constructor. And never write an object literal method as an arrow: it captures the outer this, not the object.
Rule of thumb#
To find this, ignore the definition and check the call, in order: new, then bind, then call or apply, then the object left of the dot, else undefined in strict mode (globalThis in sloppy mode). Arrows skip the list and use the outer this. For a regular function, this is not who it is, it is how it was called.
Your product here? Partner with us
Back to topDiscussion
No comments yet. Signed in GoByte members with a verified e-mail can join. Community guidelines
Reading is open to everyone. Commenting and voting need a GoByte account with a verified e-mail.