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.
In depth
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 / 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 4
- “
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
- Classes and prototypes: Prototypes and the Prototype Chain, Classes
- React handler binding patterns: 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