JAVASCRIPT / SCOPE, CLOSURES, AND THIS
Method calls and dynamic this binding
Read this in any method call from the call site, spot the moments a method loses its receiver, and keep the right object attached.
What you will learn
- Determine this by reading the expression immediately left of the last dot
- Share one function across many objects and let each call supply its own receiver
- Spot receiver loss from assignment, destructuring, or passing a method as a callback
- Restore the receiver with a wrapper call or an API's thisArg parameter
Understanding Method calls and dynamic this binding
A function in JavaScript does not carry an object around with it. `this` is an extra argument the engine passes in, and a method call is the syntax that fills it: when you write `counter.bump()`, the engine first evaluates `counter.bump` into a reference that remembers two things, the function it found and the object it looked in, and then calls the function with that object as `this`. The dot therefore belongs to the call, not to the definition, which is why one unchanged function body can report a different `this` on every line that invokes it.
That makes the rule mechanical rather than mysterious: look at the expression immediately left of the parentheses. In `app.ui.label()` the receiver is `app.ui` and not `app`, because the earlier dots only navigated to the object the call goes through; in `handlers[type]()` it is `handlers`. The search that finds the method may travel up the prototype chain, but the receiver stays where the search started, and that is exactly what lets one shared function serve thousands of instances.
The price of that flexibility is that the receiver lives in the call expression and nowhere else, so any step that reduces `obj.method` to a bare function value throws it away: storing it in a variable, destructuring it, returning it, or handing it to another function as a callback. The later invocation is then a plain call with no base, so `this` is undefined in strict code, including class bodies and modules, and it surfaces as a TypeError at the first `this.something`. When you see that, ask what stood immediately left of the parentheses at the failing call, then put an object back there.
'use strict';
const counter = {
label: 'clicks',
count: 0,
bump() {
this.count += 1;
return this.label + ': ' + this.count;
}
};
console.log(counter.bump());
const other = { label: 'views', count: 10, bump: counter.bump };
console.log(other.bump());
console.log(counter.bump());
const detached = counter.bump;
try {
detached();
} catch (err) {
console.log(err.name);
}In a method call, this is the object the call went through, that is the base of the property reference at the call site, so it is chosen again on every call instead of being stored in the function.
Worked examples
Which dot decides
Shows that only the property reference directly before the parentheses supplies this, whatever path led there.
const app = {
name: 'app',
ui: {
name: 'ui',
label() {
return this.name;
}
}
};
console.log(app.ui.label());
const panel = app.ui;
console.log(panel.label());
const copy = { name: 'copy', label: app.ui.label };
console.log(copy.label());
const key = 'label';
console.log(app.ui[key]());Example explained
Line 1app.ui.label() has base app.ui, so this.name is 'ui'; the first dot only located that object and app never becomes the receiver.
Line 2panel.label() reaches the same function through another variable pointing at the same object, so the receiver is unchanged.
Line 3copy.label holds the very same function value, and calling it through copy makes copy the receiver.
Line 4app.ui[key]() uses bracket access, but it is still a property reference with a base, so the rule applies identically.
One function, many receivers
Demonstrates that a method found on a prototype still receives the object the call started from.
const speaker = {
greet() {
return 'hi from ' + this.id;
}
};
const a = Object.create(speaker);
a.id = 'a';
const b = Object.create(speaker);
b.id = 'b';
console.log(a.greet(), b.greet());
console.log(a.greet === b.greet);
console.log(a.hasOwnProperty('greet'));Example explained
Line 1Both calls run one single function body, yet each reports its own id because this is whichever object the call went through.
Line 2a.greet === b.greet is true: there is one function object, not a per-object copy.
Line 3a.hasOwnProperty('greet') is false, so the lookup walked up to speaker, but the receiver stayed a rather than speaker.
Losing the receiver in a callback
Shows a method being detached by being passed as an argument, and two ways to put an object back at the call site.
'use strict';
const cart = {
total: 0,
add(price) {
this.total += price;
}
};
try {
[10, 20].forEach(cart.add);
} catch (err) {
console.log('detached:', err.name);
}
[10, 20].forEach(cart.add, cart);
console.log(cart.total);
[5].forEach(function (price) {
cart.add(price);
});
console.log(cart.total);Example explained
Line 1[10, 20].forEach(cart.add) reads the property once and passes only the function; cart is dropped at that moment.
Line 2forEach then invokes it with no receiver, so this is undefined under strict mode and this.total += price throws before any value is added.
Line 3The optional second argument of forEach is the receiver to use for each call, which is why forEach(cart.add, cart) brings total to 30.
Line 4The wrapper works for the same reason: cart.add(price) puts a dot back at the call site, so total ends at 35.
Important notes
Method shorthand is not a binding mechanism; it only changes details such as super support and non-constructability. A shorthand method has no memory of the object literal it was written in, so obj.method and method: function () {} follow the same receiver rule.
Parentheses that preserve the property reference keep the base, so (obj.method)() still passes obj, but anything that yields a plain value drops it, as in (0, obj.method)().
Common mistakes
Passing obj.method straight to forEach, setTimeout, or addEventListener and expecting this to remain obj: the callback runs as a plain call, so strict code throws a TypeError on the first this.x and sloppy code quietly reads and writes properties on globalThis instead.
Destructuring a method, as in const { start } = player; start();: it looks like a harmless shortcut, but the base is gone, so player is never updated and this.state is undefined or throws.
Reading a.b.c() as if this were a: it is a.b, so every property you expected from the outer object comes back undefined and comparisons silently fail instead of erroring.
Try it yourself
Change, predict, then run
In a browser console create const box = { items: [], add(x) { this.items.push(x); return this.items.length; } }, call box.add('a') through the dot, then copy box.add onto a second object with its own items array and call it there. Finally run ['b', 'c'].forEach(box.add) and ['b', 'c'].forEach(box.add, box) and explain which call kept the receiver.
Open the JavaScript workspaceCheck your understanding
A method stored on app.ui reads this.theme, and theme is a property of app, not of app.ui. What does app.ui.render() see, and why?
- this.theme is undefined, because the receiver is app.ui, the base of the property reference at the call site
- this.theme is app's value, because this is the outermost object of a dotted chain
- this.theme is app's value, because property lookup walks back through the dots that led to app.ui
- It throws a TypeError, because a method held in a nested object is always called without a receiver
Show answer
The base of the call expression is the object immediately left of the final dot, so this is app.ui, and app.ui has no theme property, which reads as undefined rather than an error. The option about lookup walking back through the dots is tempting because JavaScript really does follow a chain when a property is missing, but that is the prototype chain, and app is not on app.ui's prototype chain.