On this page
Polyfill: call, apply and bindhigh-yield
Last reviewed 22 Sept 2026
Problem
Implement myCall, myApply and myBind on Function.prototype so they behave like the built-ins:
function greet(greeting, punct) { return `${greeting}, ${this.name}${punct}`; }const user = { name: 'Asha' };
greet.myCall(user, 'Hi', '!'); // "Hi, Asha!"greet.myApply(user, ['Hello', '.']); // "Hello, Asha."const hi = greet.myBind(user, 'Hey');hi('?'); // "Hey, Asha?"Clarifying questions
- Should
null/undefinedasthisArgfall back to the global object (sloppy mode) or stayundefined(strict)? Usually: fall back toglobalThis. - Should primitives (
'abc',42) asthisArgbe boxed? Yes —Object(thisArg). - Must a bound function work with
new? Ask — it separates good answers from great ones. - Can I use the built-in
call/applyinside? No — that defeats the point.
Approach
A function called as a method (obj.fn()) gets this === obj. So to call fn with a chosen this, temporarily attach it to that object under a key that cannot collide (a Symbol), call it, then delete the key. apply is the same with an array of arguments. bind returns a new function that remembers this and the first arguments, and calls the original with apply later.
Step-by-step build
Step 1 — myCall
Function.prototype.myCall = function (thisArg, ...args) { const ctx = thisArg == null ? globalThis : Object(thisArg); const key = Symbol('fn'); // unique: never overwrites a real property ctx[key] = this; // `this` is the function being called const result = ctx[key](...args); delete ctx[key]; return result;};Step 2 — myApply
The only difference is that arguments arrive as an array (or null).
Function.prototype.myApply = function (thisArg, args = []) { return this.myCall(thisArg, ...(args ?? []));};Step 3 — myBind with partial application
Function.prototype.myBind = function (thisArg, ...preset) { const fn = this; return function (...later) { return fn.myApply(thisArg, [...preset, ...later]); };};Step 4 — support new
With the native bind, new bound() ignores the bound this and builds a fresh instance of the original function. Detect this with new.target, and link prototypes so instanceof still works.
Function.prototype.myBind = function (thisArg, ...preset) { if (typeof this !== 'function') throw new TypeError('Bind must be called on a function'); const fn = this; function bound(...later) { if (new.target) return new fn(...preset, ...later); // called with new return fn.myApply(thisArg, [...preset, ...later]); } if (fn.prototype) bound.prototype = Object.create(fn.prototype); return bound;};Final code
Function.prototype.myCall = function (thisArg, ...args) { if (typeof this !== 'function') throw new TypeError('myCall must be called on a function'); const ctx = thisArg == null ? globalThis : Object(thisArg); const key = Symbol('fn'); ctx[key] = this; try { return ctx[key](...args); } finally { delete ctx[key]; // clean up even if the function throws }};
Function.prototype.myApply = function (thisArg, args) { return this.myCall(thisArg, ...(args ?? []));};
Function.prototype.myBind = function (thisArg, ...preset) { if (typeof this !== 'function') throw new TypeError('myBind must be called on a function'); const fn = this; function bound(...later) { if (new.target) return new fn(...preset, ...later); return fn.myApply(thisArg, [...preset, ...later]); } if (fn.prototype) bound.prototype = Object.create(fn.prototype); return bound;};Edge cases
thisArgisnull/undefined→globalThis; a primitive → boxed withObject().- The target object is frozen — assigning the temporary key fails. Mention it; the real
callhas no such limit because it is implemented natively. - The function throws → the
finallystill removes the temporary key. - Binding twice:
f.myBind(a).myBind(b)keepsa— the first boundthiswins, as with the nativebind. - Arrow functions ignore
thisentirely; all three only forward the arguments.
Follow-ups
- Why a Symbol instead of
ctx.fn? A string key could overwrite an existing property. - What does
bound.lengthreturn? Nativebindsets it tomax(0, fn.length − preset.length); set it withObject.definePropertyif asked. - Implement it without spread (ES5)? Build the argument list as a string and
eval— mention it only as history.
Common mistakes
- Using an arrow function for the polyfill itself: then
thisis not the function being called. - Forgetting to delete the temporary property, leaving the object polluted.
bindthat only supportsthisand not preset arguments.- Not returning the function’s result.
Related
- Concepts: call, apply and bind and the polyfill walkthrough in the JavaScript track.
- Next: new operator and instanceof reuses the same prototype ideas.