Question 31 / 31

When every this rule applies at once, which one wins?

Topic: this bindingDifficulty: Advanced

Default, implicit, and all three explicit forms — call, apply, and bind — plus new and an arrow, chained together so each rule's override of the last one is visible in a single trace.

Solve it with the execution visualizerStep through every binding, function call, and console output below.

Predict the output

Ten calls run through the same shape of function in increasingly overridden ways. Predict all ten console lines, and be ready to say which binding rule wins at each step.

var a = "Window";

function identify() {
  console.log(this.a);
}

var obj1 = { a: "obj1", identify: identify };
var obj2 = { a: "obj2" };
var obj3 = { a: "obj3" };

identify();
obj1.identify();
obj1.identify.call(obj2);
obj1.identify.apply(obj3);

var boundIdentify = obj1.identify.bind(obj2);
boundIdentify();
boundIdentify.call(obj3);
boundIdentify.apply(obj3);

var arrowIdentify = () => console.log(this.a);
arrowIdentify.call(obj2);

function Identify(label) {
  this.a = label;
  console.log(this.a);
}

var BoundIdentify = Identify.bind(obj2);
var instance = new BoundIdentify("new-wins");
console.log(obj2.a);
Reveal the answer and explanation

Expected output

Window
obj1
obj2
obj3
obj2
obj2
obj2
Window
new-wins
obj2

Why this happens

identify() is a bare call — no object precedes it — so default binding applies and this is the global object (window), printing "Window".

obj1.identify() is a genuine method call: the receiver read off the call site is obj1, so implicit binding applies and this.a reads "obj1".

obj1.identify.call(obj2) and obj1.identify.apply(obj3) both explicitly force a receiver — call and apply differ only in how they pass arguments, not in how they bind this — so explicit binding overrides the implicit rule entirely: this is obj2, then obj3, printing "obj2" and "obj3".

obj1.identify.bind(obj2) creates a permanently-bound function. Calling boundIdentify() prints "obj2" — bind is just another explicit binding at the moment it runs.

boundIdentify.call(obj3) and boundIdentify.apply(obj3) both try to force a new receiver onto an already-bound function — and both fail, still printing "obj2". Per Function.prototype.call/apply, calling a bound function ignores the thisArg you pass it: [[BoundThis]] was fixed the moment .bind() ran, and nothing short of a fresh new can displace it.

arrowIdentify is an arrow function, so it has no this of its own — .call(obj2) is accepted syntactically but changes nothing. It closes over whatever this was where it was created (the top level, i.e. window), printing "Window" regardless of how it is invoked.

BoundIdentify is Identify hard-bound to obj2. But new BoundIdentify("new-wins") invokes it as a constructor — and per BoundFunctionExoticObject.[[Construct]], constructing a bound function ignores [[BoundThis]] entirely and constructs the original Identify with a brand-new instance as this. That instance is what receives "new-wins", printed from inside the constructor.

console.log(obj2.a) still reads "obj2" — proof that the new call above never touched the object bind() had captured. This is why new outranks even a hard bind: a constructor call always gets a freshly created object as this, never a previously bound receiver.

What this question tests

  • the full precedence, lowest to highest: default binding < implicit binding < explicit binding (call/apply/bind) < new binding — and an arrow function opts out of the whole system by using its enclosing scope's this instead
  • call and apply are the same explicit-binding rule with two different argument-passing conventions; neither one can override a receiver that bind() already locked in
  • once bind() runs, the returned function's this is fixed for its entire lifetime — no later call()/apply() (or even another bind()) can change it
  • new is the only thing that can override a hard-bound this, because constructing a bound function per spec reroutes to the original target function with a fresh instance, bypassing [[BoundThis]] altogether

Runtime assumptions

Assumes a classic sloppy-mode script, where a receiver-less call's this defaults to the global object (window).

Specification references

JavaScript Code EditorDirectly edit or paste custom code
Step 0 of 0
StepSpaceRun
Press → (Step) or Space (Run)
Phase 1: Compilation(Hoisting & Memory Setup)
Phase 2: Execution(Line-by-line Evaluation)
Execution Step Breakdown

Press "→" / click "▶" to step line-by-line, or press "Space" / click "▶ Run" for auto-play.