this and Function Binding
Prerequisites: you are comfortable with function declarations vs expressions, closures, and the call stack. You already know this exists and roughly that "it depends on how the function is called." This chapter gives you the full binding model, the failure modes, and the decision criteria for choosing between arrows, bind, and explicit invocation.
The Four Binding Rules
this is not a property of the function itself. It is a hidden argument supplied at call time, resolved by the first matching rule below.
- New binding:
new f()—thisis the freshly created object. - Explicit binding:
f.call(x),f.apply(x),f.bind(x)—thisisx(except when hard-bound; see below). - Implicit binding:
obj.f()—thisisobj, the receiver to the left of the dot at the call site. - Default binding: a bare call
f()—thisisundefinedin strict mode,globalThisin sloppy mode.
Rules are checked in that order. Notably, new beats explicit binding for ordinary functions, but a hard-bound function ignores both new and further calls — bind is a one-way commitment. Only the explicit this argument of bound functions is locked; arguments passed to bind after the first are partially applied and prepend to later calls.
'use strict';
function show(...args) {
return [this, ...args];
}
const bound = show.bind('LOCKED', 1);
console.log(bound()); // ['LOCKED', 1]
console.log(bound.call('other', 2)); // ['LOCKED', 1, 2] — this ignored
console.log(new (show.bind('x'))()); // {} — new wins only via this-barrier semanticsThe receiver is determined lexically at the call site, which is why method extraction breaks:
const counter = {
n: 0,
inc() { this.n += 1; return this.n; },
};
counter.inc(); // 1 — implicit binding
const inc = counter.inc;
inc(); // TypeError: Cannot set properties of undefinedOnly the last property before the call matters: a.b.c.f() binds this to c.
Resolution order diagram
Arrow Functions: Lexical this
Arrow functions have no this binding of their own. A reference to this inside an arrow is an ordinary closure variable, resolved lexically to the enclosing function's this. Consequently:
call,apply, andbindcannot change an arrow'sthis; their first argument is silently ignored (thoughbindstill partially applies arguments).- Arrows cannot be used as constructors.
- Arrows have no
argumentsobject; use rest parameters.
This makes arrows the right tool when you want to delegate to the surrounding this, and the wrong tool when a function will ever be called as a method or constructor.
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds += 1; // lexical this: timer
}, 1000);
},
};Where arrows bite back
- Prototype/class methods defined as arrows on instances: each instance gets its own function;
superinside an arrow refers to the enclosing class's home, which can produce surprisingsuperreferences in object literals. - Event handlers: DOM event handlers are invoked with the element as receiver. An arrow captures the defining
thisinstead, which is either what you wanted (a closure) or a bug (you neededevent.currentTarget). - Object literals:
thisin a shorthand arrow property resolves to the enclosing scope'sthis, not the object — oftenundefinedat module scope.
Internal Mechanics and Edge Cases
super.method()inside a class performs a dynamicthis-based dispatch but a static home-object lookup for the prototype. You cannot replicate it withObject.getPrototypeOf(this).method.call(this)in subclasses that override the method — it would recurse.Function.prototype.bindpolyfill limitations: a native bound function preserves arity (length) up to the bound arguments, has anameprefixed withbound, and is exotic — it cannot be re-bound and forwardsnewcorrectly to the target. Polyfilled versions historically failednew.Reflect.apply(f, thisArg, args)is the clean primitive;f.apply(thisArg, args)differs only in thatapplycan be shadowed or detached from the function.- Callbacks in class fields: class field arrow functions (
handleClick = () => {...}) bind at construction time, one closure per instance — a memory and equality trade-off vs. a bound prototype method created once per class. - Sloppy-mode default
this=globalThisis a boxing hazard historically (primitives asthiswere boxed in sloppy mode); strict mode passesthisthrough uncoerced. Always run modules (which are strict) or add'use strict'. thisin top-level module code isundefined; in a classic script it isglobalThis.
Decision Criteria
| Situation | Use | Why |
|---|---|---|
| Inner callback needing outer `this` | Arrow function | Lexical capture, no per-call allocation |
| Method to be detached/reused | `bind` or class-field arrow | Pre-commit the receiver |
| Borrowing a method once | `call` / `Reflect.apply` | No retained closure |
| Function used with `new` | Ordinary function/class | Arrows throw |
| Generic utility ignoring receiver | Arrow or `() =>` at module scope | Avoid accidental implicit binding |
Failure modes to watch: double-binding (.bind() on an already-bound function does nothing to this — debug by checking fn.name for the bound prefix); lost receivers through destructured method references (const { log } = console works because log ignores this, but const { push } = arr throws); and React-style "bound in constructor vs. class-field arrow vs. inline arrow at JSX call site" — the inline arrow re-allocates per render, which matters for memoized children.
Exercise
Why does const inc = counter.inc; inc() lose this, while counter.inc() works, even though both invoke the same function object?
Show answer
this is chosen at call time from the call-site syntax. In counter.inc() the receiver is counter (implicit binding). In inc() there is no receiver, so default binding applies — undefined under strict mode. The function object itself never stores this.
Exercise
What does f.bind(a).call(b) log if f logs this? What about f.call(a).bind — why is that expression invalid, and what is the correct chained form?
Show answer
f.bind(a).call(b) logs a — hard binding locks this, later call arguments to this are ignored. f.call(a).bind is invalid because f.call(a) invokes f and its return value is whatever f returns, not a function; to partially apply you must bind before calling: f.bind(a, ...presetArgs).
Exercise
You replace setInterval(function () { this.tick(); }, 1000) inside a method with an arrow and it works. Name one context where the same substitution silently breaks behavior.
Show answer
When the function is used as a DOM event handler or instance method that must receive the caller as receiver — e.g. element.addEventListener('click', ...) where you need the element. The arrow's lexical this is the defining object, not the event target; you'd need event.currentTarget.
Spotted a mistake, or code that does not work?