The this keyword in JavaScript - four binding rules in one place

this in JS only looks weird because people look in the wrong place. Its value is decided at the moment of the call - and there are only four rules the call can pick from, plus arrow functions, which break out of the whole system.
Classic this gotcha in JavaScript:
const user = {
name: "Adam",
greet() {
console.log(`Hi, ${this.name}`);
},
};
user.greet(); // → "Hi, Adam"
const fn = user.greet;
fn(); // → "Hi, undefined"Same function, same code inside, two different results. this points to the user object once, and to something else the next time. What’s going on?
The key: this is not a property of the function - it’s a property of the call. You won’t figure out what this points to by looking at the function. You’ll figure it out by looking at how the function was called.
In JavaScript there are exactly four rules of calling that decide the value of this. Plus arrow functions, which break out of the whole system. This post is about how to tell which rule fires.
#this is decided at the call, not at the definition
When you write a function, you don’t bake any decision about this into it. That field is empty, waiting to be filled. The decision is made at the moment someone calls the function - and it’s made based on how that someone calls it.
That’s why the example above works the way it does: user.greet() is a call involving an object (user), so this gets bound to that object. fn() is a bare call - the object disappeared from the call site, so this ends up bound to something else.
Operational takeaway: to figure out what this points to, look at how the function was called, not at its definition.
#Four call rules
Four call patterns the engine recognizes, plus a fifth case (arrow function) that doesn’t fit any of them. Click between the tabs - each example shows how a different call gives a different this:
const user = {
name: "Adam",
greet() {
console.log(this.name);
}
};
user.greet();user"Adam"The user object sits right before the dot at the call site, so this gets bound to that object.
In short:
- Default binding - bare
fn()with no dot in front.thisends up onglobalThis(non-strict mode) orundefined(strict mode and ESM modules, which are strict by default). - Implicit binding -
obj.method(). What matters is what sits right before the dot. Trap:const fn = obj.method; fn()loses the binding (falls back to default). - Explicit binding -
.call(thisArg),.apply(thisArg),.bind(thisArg). You setthisexplicitly..bindis “hard binding” - it returns a new function whosethiscan’t be changed afterwards, not even by.call. newbinding -new Foo(). The engine creates a new object and sets it asthis. The full mechanism (linking to the prototype, return value handling) is covered in the post aboutnew.
#Rule priorities
What if multiple rules theoretically apply? Hierarchy, top to bottom:
newbeats everything (neweven beats.bind)- Explicit (
.call/.apply/.bind) beats implicit - Implicit (
obj.method()) beats default - Default is the fallback
In practice: look at the call, identify the highest-priority rule that applies - that’s your rule.
ECMA-262 specifies this formally in §10.2.1.1 OrdinaryCallBindThis - an abstract operation that runs on every regular function call and picks the appropriate binding based on the internal [[ThisMode]] slot.
#Arrow functions: the exception to the system
Arrow functions BREAK OUT of the four rules. They don’t have their own this. When you write this inside an arrow function, JavaScript treats it like any other variable - it looks for it in the lexical scope, that is, where the arrow function was defined.
The example in the widget above (the () => this tab) shows it precisely: the arrow inside setTimeout has no this of its own, so it looks in the enclosing scope. The enclosing scope is the greet() method, where this is user (implicit binding). So the arrow “sees” user as its this.
Mental shortcut: an arrow function is “transparent” for this - it lets this from the surroundings pass through. A regular function is “opaque” - it gets its own this according to the call site.
#Takeaway
The whole canon of this boils down to two things:
thisis decided at the call, not at the definition. Look at how the function was called - that’s all you need.- Rule hierarchy:
new> explicit > implicit > default. Arrow functions break out of this hierarchy (no ownthis, drawn lexically from the surroundings).
If you see this in code somewhere and don’t know what it points to: find the call, match it against the hierarchy, and you have your answer.
Comments
Loading comments…