Custom bind, call and apply
Key Learning Objectives
Understand how JavaScript binds the execution context ('this') dynamically at invocation time.
Implement educational customCall and customApply by temporarily binding the function as a Symbol property on the target context.
Implement customBind returning a wrapped function that merges preset arguments with invocation arguments (partial application).
Handle constructor invocation with the 'new' operator: recognize that 'new' overrides bound 'this' and binds the new instance.
Preserve prototype chains between bound functions and original constructor prototypes using Object.create(fn.prototype).
The Interview Problem
What is logged to the console when custom implementations of call, apply, and bind execute with argument forwarding and constructor invocation?
1function customCall(fn, context, ...args) {2 const ctx = context ?? (typeof window !== 'undefined' ? window : globalThis);3 const key = Symbol('fn');4 ctx[key] = fn;5 const result = ctx[key](...args);6 delete ctx[key];7 return result;8}910function customApply(fn, context, args = []) {11 return customCall(fn, context, ...args);12}1314function customBind(fn, context, ...boundArgs) {15 function boundFunction(...callArgs) {16 const isNew = this instanceof boundFunction;17 const targetCtx = isNew ? this : context;18 return fn.apply(targetCtx, [...boundArgs, ...callArgs]);19 }20 if (fn.prototype) {21 boundFunction.prototype = Object.create(fn.prototype);22 }23 return boundFunction;24}2526function greet(greeting, punctuation) {27 return greeting + ', ' + this.name + punctuation;28}2930function Person(name, role) {31 this.name = name;32 this.role = role;33}3435const user = { name: 'Sarah' };3637const r1 = customCall(greet, user, 'Hello', '!');38const r2 = customApply(greet, user, ['Hi', '?']);3940const BoundPerson = customBind(Person, { name: 'IgnoredCtx' }, 'Alex');41const p1 = new BoundPerson('Staff Eng');4243const isInstanceOfPerson = p1 instanceof Person;44const personName = p1.name;45const personRole = p1.role;4647console.log([r1, r2, isInstanceOfPerson, personName, personRole].join(' | '));
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
Hello, Sarah! | Hi, Sarah? | true | Alex | Staff Eng
Hello, Sarah! | Hi, Sarah? | false | IgnoredCtx | Staff Eng
Hello, undefined! | Hi, undefined? | true | Alex | Staff Eng
Hello, Sarah! | Hi, Sarah? | true | undefined | Staff Eng
V8 Engine Execution Trace
Step 1 of 6 (Line 34)customCall temporarily assigns user[Symbol('fn')] = greet and invokes it, producing 'Hello, Sarah!'. The Symbol key is deleted immediately afterward.
Deep Technical Breakdown
Dynamic Execution Contexts in JavaScript
Every non-arrow JavaScript function receives a dynamic this binding determined at the call site. The language provides three standard methods on Function.prototype to control this explicitly:
| Method | Execution Timing | Arguments Format | Returns |
|---|---|---|---|
call(ctx, arg1, arg2) | Immediate | Comma-separated list | Function return value |
apply(ctx, [arg1, arg2]) | Immediate | Array of arguments | Function return value |
bind(ctx, arg1, arg2) | Deferred | Comma-separated list | New bound function |
The Symbol Method Technique
To invoke a function with an explicit this without native .call():
- Generate a unique property key:
const key = Symbol(); - Attach the function:
context[key] = fn; - Invoke:
const res = context[key](...args); - Clean up:
delete context[key]; - Return result.
The new Operator Override Rule
When a bound function is called as a constructor (new BoundFn()):
const isNew = this instanceof boundFunction;
const targetCtx = isNew ? this : context;ECMAScript specification explicitly dictates that when new is used on a bound function, the originally bound context is discarded, and this is bound to the freshly instantiated object inheriting from the target function's prototype.
Common Traps & Mistakes
Using a string key like `context.__fn__ = fn`, which can overwrite existing object properties or trigger prototype pollution.
Forgetting to delete the temporary Symbol property after invocation, leaving leftover references on target objects.
Ignoring the `new` operator in `bind`: failing to detect `this instanceof boundFunction` causes constructor calls to mutate the bound context instead of the new instance.
Failing to link prototypes: if `boundFunction.prototype` does not inherit from `fn.prototype`, `instanceof` checks will return false.
FAANG Follow-Up Probes
Probe #1
What happens when you call `.bind()` multiple times on the same function? Which `this` context wins and why?
Probe #2
Why can arrow functions never have their `this` context rebound using call, apply, or bind?
Probe #3
How does the performance of native `Function.prototype.bind` compare to closure-based polyfills in V8 engines?
