You are writing a generic wrapper that logs every call to a method it is given. How do you make sure the wrapped method still receives the correct this, and where does that approach break down?
answer
- the wrapper needs a receiver of its own
- take it from the call site, pass it down
- one function form cannot do this
- transparent in behaviour, not in identity
basics
~20 sReturn a regular function that invokes the target with fn.apply(this, args). The wrapper picks up the receiver from its own call site and forwards it. An arrow wrapper cannot do this, and calling fn(...args) drops the receiver entirely.
solid answer
~50 sThe wrapper must be an ordinary `function`, not an arrow, because it needs its own `this` — the receiver the call site supplies when the wrapper is invoked as a method. Inside, forward it explicitly: `return function (...args) { log(args); return fn.apply(this, args); }`. Using `fn(...args)` calls the target with no receiver, so `this` becomes `undefined` in strict code and every property access on it throws; using an arrow wrapper captures the definition-site `this` instead, which is almost never the object the method was later attached to. The approach still has limits: the wrapper is a different function object, so `name`, `length` and identity change; it is not a drop-in constructor, since `new wrapper()` builds an object with the wrapper's prototype rather than the target's; and if the incoming function is already bound or is an arrow, the forwarded receiver is ignored and there is nothing you can do about it.
code
javascript · 19 linesfunction logged(fn) {
return function (...args) {
console.log('call', args);
return fn.apply(this, args);
};
}
const store = {
name: 'store',
save(n) { return this.name + ':' + n; },
};
store.save = logged(store.save);
console.log(store.save(1)); // logs, then 'store:1'
// Dropping the receiver instead:
const broken = (fn) => function (...a) { return fn(...a); };
const other = { name: 'x', save: broken(function () { return this.name; }) };
console.log(other.save()); // undefined in sloppy mode, TypeError in strictgo deeper
Know the shape of the fix: the wrapper is a regular function and it passes its own this along with fn.apply(this, args) rather than calling fn directly.
Explain why an arrow cannot forward a receiver — it has no this binding and resolves lexically — and what happens to this when the receiver is dropped in strict versus sloppy code.
Show what transparency actually costs: changed identity, name and length, broken construction semantics, and property descriptors that a plain assignment silently flattens.
Decide the policy: where instrumentation is applied — the prototype, the instance, or the call site — determines whether receivers are still live, and a library should state which functions it is safe to wrap.
## The requirement A wrapper is transparent when the target cannot tell it is there. For a method, that includes the receiver: if `obj.save()` used to run with `this === obj`, it must still do so after wrapping. Nothing about a closure gives you that automatically — a wrapper has to capture the receiver at call time and pass it on. ## The correct shape ```js function logged(fn) { return function (...args) { console.log('calling with', args); return fn.apply(this, args); }; } const obj = { name: 'store', save(n) { return this.name + ':' + n; }, }; obj.save = logged(obj.save); obj.save(1); // 'store:1' ``` Two things make it work. The wrapper is declared with the `function` keyword, so it gets its own `this` binding, filled in by whatever call form is used — here the implicit receiver from `obj.save(...)`. And `fn.apply(this, args)` hands that exact value to the target. `fn.call(this, ...args)` is equivalent; with a rest array already in hand, `apply` is the natural fit. ## The two ways to get it wrong **Dropping the receiver.** `return fn(...args)` is a plain call: the target gets no receiver at all. In strict-mode code — which is all module and class code — `this` is then `undefined`, and the first property access throws `TypeError: Cannot read properties of undefined`. In sloppy code it is worse, because `this` silently becomes `globalThis` and the method reads or writes global state instead of failing. **Using an arrow for the wrapper.** An arrow function has no `this` binding of its own; it resolves `this` lexically from where it was written. That is the definition site inside `logged`, not the call site of the wrapper. Assigning the arrow as a method changes nothing — the receiver at the call site is simply discarded. Arrows are the right tool for *capturing* a surrounding receiver and the wrong tool for *forwarding* an incoming one. ```js const broken = (fn) => (...args) => fn.apply(this, args); // `this` is not the caller's ``` ## Where the correct version still falls short Even done right, the wrapper is a different function object, so several observable things change: - **Identity.** `wrapped !== original`. Any registry, cache or set keyed on the function object will not find the original any more, and you cannot remove a handler you registered before wrapping by passing the wrapper. - **Metadata.** A rest-parameter wrapper reports `length` 0 and takes its `name` from the variable it is assigned to, so code that introspects arity or name sees the wrapper. You can repair this deliberately with `Object.defineProperty(wrapped, 'length', { value: fn.length })` and the same for `name`, since both are configurable. - **Construction.** `new wrapped()` does not reproduce `new original()`. The new object gets `wrapped.prototype`, the target runs as an ordinary function against it, and the result fails `instanceof original`. A wrapper meant to cover constructors needs a different technique. - **Property access.** Wrapping a method replaces the value on the object, so getters, setters and non-enumerable or non-writable properties need `Object.getOwnPropertyDescriptor` and `defineProperty` rather than a plain assignment, or you will silently change the shape of the object. - **Async.** Returning `fn.apply(this, args)` propagates the promise correctly, but a timing log must `await` or attach to the returned promise; measuring only the synchronous portion is a classic wrong result. ## The case you cannot fix If the function handed to your wrapper is already hard-bound with `bind`, or is an arrow, forwarding the receiver has no effect: a bound function ignores the receiver it is called with, and an arrow has no receiver slot to fill. Your wrapper still behaves correctly — the target simply does not consult what you passed. This is worth saying out loud in an interview, because it shows you know that receiver control belongs to whoever created the function value, not to whoever calls it. The practical rule for a library is to wrap the *method as declared on the object or prototype*, before anyone binds it, so that the receiver is still live. ## Summary answer Use a non-arrow wrapper, forward with `apply(this, args)`, return the target's value unchanged, and know that identity, arity, name and construction semantics are not preserved for free — and that an already-bound or arrow target makes the forwarding moot.
- What does the wrapper fail to preserve about the original function, and can any of it be restored?Identity, `name`, `length`, and constructibility. A rest-parameter wrapper reports `length` 0 and takes its `name` from the binding it is assigned to; both properties are configurable, so `Object.defineProperty` can copy the originals across. Identity cannot be restored — the wrapper is a different object — and `new wrapper()` still produces an instance of the wrapper, not the target.
- What happens if the function passed to the wrapper is already bound?The forwarding becomes a no-op. A bound function ignores whatever receiver it is invoked with, so `fn.apply(this, args)` delivers your value and the target discards it, running with the receiver fixed at bind time. The wrapper is still correct; the target simply is not listening. The fix is to wrap the method as declared, before anything binds it.
- Why is fn.call(this, ...args) equivalent here, and is there a reason to prefer one?They differ only in argument shape — `call` spreads, `apply` takes an array — and both set the same receiver. With a rest parameter you already hold an array, so `apply` avoids re-spreading it. For very large argument lists both build a real argument list and can hit the engine's argument limit, so neither escapes that ceiling.
saying these in an interview costs you the question
- Uses an arrow function as the forwarding wrapper
- Calls fn(...args) and loses the receiver
- Assumes the wrapper is a drop-in constructor
- Thinks wrapping preserves function identity
- Believes forwarding can override an already-bound this