this and Binding
TL;DR
this is not lexical for normal functions — it’s set by how the function is called, not where it’s defined. Four call patterns decide it: method call (obj.fn() → obj), plain call (fn() → undefined in strict mode / globalThis otherwise), new (a fresh object), and explicit call/apply/bind. Arrow functions are the exception: they have no own this and capture it lexically from the enclosing scope. The classic bug is method extraction — pulling a method off its object loses the binding.
Interview Q&A
Q: What determines this in a normal function?
A: The call site. Same function, four bindings:
function show() { return this; }
const obj = { show };
obj.show(); // obj (method call)
show(); // undefined (plain call, strict mode)
new show(); // {} (construction — new object)
show.call("x"); // "x" (explicit)
Resolution priority: new > explicit (call/apply/bind) > method > plain.
Q: How do arrow functions differ?
A: Arrows have no own this, arguments, super, or new.target. They close over this from the surrounding lexical scope at definition time, and it can’t be rebound:
const obj = {
id: 1,
regular() { return [1].map(function () { return this.id; }); }, // [undefined] — inner `this` is not obj
arrow() { return [1].map(() => this.id); }, // [1] — arrow captures obj
};
This is exactly why arrows are the fix for “lost this” inside callbacks.
Q: What’s the method-extraction pitfall?
A: Assigning a method to a variable (or passing it as a callback) detaches it from its receiver:
const counter = {
count: 0,
inc() { this.count++; },
};
const f = counter.inc;
f(); // TypeError: Cannot read properties of undefined (reading 'count')
setTimeout(counter.inc, 0); // same problem — called as a plain function
Fixes: setTimeout(() => counter.inc(), 0), setTimeout(counter.inc.bind(counter), 0), or define inc as a class field arrow.
Q: call vs apply vs bind?
A:
| Invokes now? | Args | |
|---|---|---|
fn.call(thisArg, a, b) |
yes | listed individually |
fn.apply(thisArg, [a, b]) |
yes | as an array |
fn.bind(thisArg, a) |
no — returns a new function | optionally pre-fills (partial application) |
bind permanently fixes this (and any bound args) — a bound function cannot be re-bound, and new on a bound function ignores the bound this but keeps bound args.
Q: Class fields as arrows vs prototype methods — trade-off?
A:
class Btn {
onClick = () => { this.handle(); }; // arrow field: bound per instance, survives extraction
handle() {} // prototype method: shared, but `this` depends on call site
}
Arrow fields auto-bind (great for React event handlers, no bind in constructor) but create one function per instance (memory) and live on the instance, not the prototype — so they’re harder to spy/override in tests. Prototype methods are shared and overridable but need binding when passed as callbacks.
Q: What is this at module top level and in plain functions under strict mode?
A: In an ES module, top-level this is undefined. In a CommonJS module it’s module.exports. Inside a plain (non-method) function call, this is undefined under "use strict" (and all ES-module/class code is implicitly strict), or globalThis in sloppy mode.
Gotchas / edge cases
thisin a standalone callback is not the object —arr.forEach(obj.method)callsmethodwiththis === undefined. Usearr.forEach(obj.method, obj)(forEach takes athisArg) or wrap in an arrow.bindthenbindagain does nothing — the first binding wins; the second is ignored.- Arrow as a method loses the object —
{ id: 1, get() { } }works, but{ id: 1, get: () => this.id }captures module/globalthis, not the object. new-ing an arrow throws — arrows are not constructors.- Event handlers: a normal
functionhandler hasthis === the element; an arrow handler has the lexicalthis. React passes the event, so it rarely matters there, but it does with raw DOMaddEventListener. applywith a huge array can hit argument-count limits — use spread (fn(...bigArray)) only for moderate sizes; for max/min over large arrays, reduce instead.
What a senior is expected to say
- “
thisis dynamic for normal functions — decided by the call site, with prioritynew>bind/call> method > plain. Arrows capturethislexically and can’t be rebound.” - “Method extraction loses the receiver; I fix it with an arrow wrapper,
bind, or a class arrow field.” - “Class arrow fields auto-bind but cost one function per instance and skip the prototype — fine for handlers, not for hot, shared methods.”
- “All class and module code is strict mode, so a plain call gives
this === undefined, not the global object.”
Cross-references
- Closures (lexical scope, the other half of the story): closures.md
- Classes and prototypes: prototypes.md, classes.md
- React handler binding patterns: ../05_react/
Further reading
- MDN —
this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this - MDN —
Function.prototype.bind: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind - You Don’t Know JS —
this& Object Prototypes: https://github.com/getify/You-Dont-Know-JS