skip to content

In JavaScript, counter.increment() works, but `const inc = counter.increment; inc();` throws because this is undefined. Explain why pulling a method out of its object loses the receiver.

level: juniorimportance: must knowfreq 78%

answer

  1. call site decides, not the definition
  2. what sits left of the dot
  3. assignment keeps the function, drops the base
  4. plain call falls back to default binding
  5. strict undefined, sloppy globalThis

basics

~20 s

JavaScript decides this at call time from whatever sits to the left of the dot, not where the function was written. Extracting a method copies only the function value, so a later plain call has no receiver and this falls back to undefined or globalThis.

solid answer

~40 s

In JavaScript a function does not carry the object it was written on. The `this` value is supplied by the **call site**: `counter.increment()` is a method call, so the receiver is `counter`. Writing `const inc = counter.increment` evaluates the property and hands you the bare function object; the association with `counter` was never part of that value. Calling `inc()` is then a plain call, which uses the default binding — `undefined` in strict-mode code (class bodies and ES modules are always strict) and `globalThis` in sloppy mode. This is exactly what happens when you write `setTimeout(counter.increment, 100)` or register `this.handleClick` as a callback: the property access happens once, at the moment you pass the reference, and the receiver is dropped right there.

code

javascript · 17 lines
javascript
const counter = {
  count: 0,
  increment() { this.count++; return this.count; }
};

console.log(counter.increment()); // 1 — dot supplies the receiver

const inc = counter.increment;    // receiver dropped here, not later
console.log(inc === counter.increment); // true: same function object

try {
  inc();                          // plain call in a module (strict)
} catch (e) {
  console.log(e.constructor.name); // 'TypeError'
}

console.log(counter.count);       // still 1

go deeper

for a junior

Be able to say plainly that this comes from the call, and that obj.method passed as a callback arrives without its object. Know at least one fix — wrapping the call in an arrow function — and be able to write it.

for a middle

Explain the mechanics: the property access yields a Reference whose base becomes the receiver, assignment discards that base, and the resulting plain call falls back to undefined in strict code or globalThis in sloppy code.

for a senior

Show that you know the receiver is supplied by whoever calls, so callback APIs hand your method a receiver of their own choosing. Talk about which fix you apply where, and about designing a surface callers cannot misuse.

for a principal

Frame it as an API-design constraint: any surface exported as detachable methods puts a correctness rule on every consumer. Argue when a codebase should expose closures or pre-bound handlers instead of raw methods, and what that costs in allocations.

## The rule in one line A JavaScript function has no permanent owner. `this` is not captured when the function is defined — it is an implicit parameter filled in by whoever calls it, decided by the shape of the call expression. ## What a method call actually does ```js const counter = { count: 0, increment() { this.count++; } }; counter.increment(); // count -> 1 ``` The expression `counter.increment` produces an internal *Reference*: a pair of "the base value `counter`" and "the property name `increment`". When that reference is immediately followed by `()`, the engine calls the function **and passes the base value as `this`**. The dot is not decoration; it is what supplies the receiver. ## What extraction does ```js const inc = counter.increment; ``` Assignment cannot store a Reference in a variable, so the engine resolves it to the plain function object and throws the base value away. `inc` and `counter.increment` are now the same function object — `inc === counter.increment` is `true` — but `inc` carries no memory of `counter`. Calling `inc()` is a plain call with no base value, so the default binding applies: ```js inc(); // TypeError: Cannot read properties of undefined (reading 'count') ``` Because objects created with `class` bodies and any code inside an ES module are strict mode, `this` is `undefined` there, and the first property access on it throws. In a sloppy-mode classic script the default binding substitutes `globalThis`, which is worse: nothing throws, `globalThis.count` becomes `NaN`, and the real object never changes. ## Why the callback case looks like magic The two shapes below are the same bug wearing different clothes: ```js setTimeout(counter.increment, 100); // property read now, called later button.addEventListener('click', counter.increment); ``` The property access `counter.increment` runs *immediately*, at registration time, and its result — a bare function — is what the callback API stores. When the API eventually calls it, the receiver comes from the API, not from you. That receiver is never your object: a browser timer callback is invoked with the global object, Node's timers invoke it with a `Timeout` object, and a DOM event listener is invoked with the element the listener is attached to. So "`this` is undefined" is only the strict-mode plain-call case; the general statement is stronger and more useful — **the receiver becomes whatever the caller supplies, which is never the object you took the method from.** ## Proving it to yourself ```js function call(fn) { return fn(); } // plain call inside const o = { tag: 'o', who() { return this?.tag; } }; o.who(); // 'o' — dot supplies the receiver call(o.who); // undefined — extracted, then called plainly ``` Nothing about `who` changed between the two lines. Only the call expression changed. ## Keeping the receiver Three things restore it, and all three work by making sure a receiver exists at the moment of the call: ```js setTimeout(() => counter.increment(), 100); // call it as a method inside a wrapper setTimeout(counter.increment.bind(counter), 100); // ship a function with a fixed receiver [1, 2].forEach(counter.increment, counter); // built-ins that accept a thisArg ``` Note what does *not* help: extra arguments to `setTimeout` after the delay are passed to the callback as **arguments**, not as a receiver, and adding `'use strict'` only changes `globalThis` into `undefined` — it turns a silent bug into a loud one, which is an improvement in diagnosability but not a fix. ## The interview point Candidates who have only memorised "arrow functions fix `this`" cannot explain *why* the wrapper works. The explanation an interviewer wants is: the receiver is an argument of the call, the dot is what supplies it, and passing `obj.method` around performs the property read early and discards the receiver — so you must re-establish one, either at the call site or by producing a function that carries its own.

  • If `inc === counter.increment` is true, why does only one of them work?
    Because they are the same function object but not the same *call expression*. The receiver is an argument the call supplies, and `counter.increment()` supplies `counter` while `inc()` supplies nothing. Identity of the function tells you nothing about the receiver — that is decided fresh on every invocation.
  • Does passing a third argument to setTimeout give the callback its receiver?
    No. Arguments after the delay are forwarded to the callback as ordinary parameters, not as `this`. `setTimeout(counter.increment, 0, counter)` calls `increment(counter)` with the receiver still missing. To supply a receiver you need a wrapper that calls it as a method, or a function that already carries one.
  • Is `this` always undefined when a method is used as a callback?
    No — that is only the strict-mode plain-call outcome. Callback APIs often supply a receiver of their own: a DOM event listener is invoked with the element it is attached to, a browser timer callback with the global object, a Node timer callback with a `Timeout` object. The reliable statement is that the receiver is whatever the caller passes, never your object.

this behaves like the word "here" in a spoken sentence: it means wherever it is said, not wherever it was written down. Copying a sentence onto a note does not make "here" keep pointing at the room it came from.

saying these in an interview costs you the question

  • Says this is fixed when the function is defined
  • Believes the function remembers the object it was written on
  • Thinks adding 'use strict' fixes the lost receiver
  • Claims setTimeout's extra arguments set this
  • Assumes this is always undefined inside a callback

context