this and Binding
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.
What determines this in a normal function?
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.
How do arrow functions differ?
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
// [1] — arrow captures obj
arrow() { return [1].map(() => this.id); },
};This is exactly why arrows are the fix for “lost this” inside callbacks.
What’s the method-extraction pitfall?
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 functionFixes: setTimeout(() => counter.inc(), 0), setTimeout(counter.inc.bind(counter), 0), or define inc as a class field arrow.
call vs apply vs bind?
| 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.
Class fields as arrows vs prototype methods — trade-off?
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.
What is this at module top level and in plain functions under strict mode?
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
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.
Related
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
Interview angle 3
- “What determines
thisin a normal function?” - the call site, not where the function was written. The precedence isnew, then an explicitbind/call/apply, then a method call on a receiver, then a plain call. Arrow functions have nothisof their own - they close over the enclosing scope’s, and cannot be rebound at all. - “You pass
obj.methodas a callback and it breaks. Why?” - extracting the function separates it from its receiver, so at call time there is no object on the left of the dot. Wrap it in an arrow,bindit, or make it a class arrow field.- Follow-up: “Which of those would you choose?” - an arrow wrapper at the call site for a one-off. A class arrow field auto-binds, but it allocates a function per instance and lives off the prototype, which is fine for event handlers and wrong for a hot method shared by many instances.
- “A plain function call gives
undefinedinstead ofwindow. Why?” - strict mode, which all class bodies and all modules are in by default. The old sloppy-mode behaviour of substituting the global object does not apply, and that is a better default because the failure is loud.