AcademyJavaScript
How this is decided at call time
The four rules that fix the value of this at call time, why extracting a method breaks it, and what arrow functions actually change.
3 min readUpdated 3 exercises
this is not about where the function was written
For a normal function, this is decided at the moment of the call, by how it is called — not by where it was defined, not by which object it happens to be stored on. The same function can have a different this on every call.
Four rules, checked in order. The first that applies wins.
1. new — a fresh object
function User(name) { this.name = name; }
const u = new User('Ada'); // this === the new object2. An explicit call, apply or bind
greet.call(obj); // this === obj
greet.apply(obj, args); // same, arguments as an array
const bound = greet.bind(obj); // permanently, and it cannot be rebound3. A method call — the object before the dot
obj.method(); // this === obj
arr[0](); // this === arrThe key word is before the dot at the call site. Not "the object it belongs to".
4. Nothing — the default
plain(); // undefined in modules and strict mode,
// globalThis in sloppy modeThe bug this produces, every time
const counter = {
count: 0,
increment() { this.count++; },
};
const inc = counter.increment;
inc(); // TypeError: Cannot read properties of undefinedinc is the same function. What was lost is the call site — there is no object before the dot any more, so rule 4 applies and this is undefined.
This is why passing a method as a callback fails:
button.addEventListener('click', counter.increment); // broken
setTimeout(counter.increment, 100); // broken
[1, 2].forEach(counter.increment); // brokenThree fixes, in order of preference: counter.increment.bind(counter), an arrow wrapper () => counter.increment(), or a class field increment = () => { ... } which binds once per instance at construction.
What arrow functions actually do
An arrow function has no `this` of its own. It is not "bound to the enclosing scope" by some special mechanism — this inside it is simply not a thing it defines, so the identifier resolves outward like any other variable, to whatever this the enclosing function had.
That is exactly why the old workaround disappeared:
// before
const self = this;
items.forEach(function (i) { self.total += i; });
// after
items.forEach((i) => { this.total += i; });And exactly why an arrow is the wrong choice for a method on an object literal:
const obj = {
name: 'Ada',
greet: () => `Hi ${this.name}`, // this is NOT obj
};There is no enclosing function, so this is the module or global this — never obj. Rules 2 and 3 do not apply to arrows at all: arrow.call(obj) is ignored, silently.
Classes are strict, always
Class bodies run in strict mode whatever the surrounding file does, so a detached method gives you undefined rather than the global object. That is a mercy: a TypeError at the call is far easier to find than properties quietly written onto globalThis.
A quick way to settle any
thisargument: look only at the call site. Is there anew? An explicit.call/.bind? A dot? Nothing? The answer is in that one line — unless the function is an arrow, in which case the answer is wherever it was written.
Now practice it
3 exercisesReading this page is the cheap half. These are the exercises that make you use it.
- Object-Oriented Programming · Classes & ObjectsObjects, methods and construction — where the first three rules show up in practice.QuizTake · EasyQuizTake · Easy
- Object-Oriented Programming · Inheritance & PrototypesInheritance and prototypes, including what `this` does across a `super` call.QuizTake · MediumQuizTake · Medium
- Output & Coercion Traps · Tricky OutputsPredict-the-output snippets, which is the format every `this` question is really asked in.QuizTake · MediumQuizTake · Medium
Read next
Closures in JavaScript
What a closure is, why the variable outlives the function that created it, and the loop bug every JavaScript developer hits once.
3 min
Prototypes and the prototype chain
How property lookup really works, what class is syntax for, and the difference between prototype and __proto__ that trips everyone up.
3 min