Function.prototype.bind Polyfill
Implement Function.prototype.bind: bound this, prepended arguments, and new Bound() that still uses the original function prototype.
- javascript
- this
- polyfill
- interview
A Function.prototype.bind polyfill that only closes over thisArg and returns fn.apply(thisArg, args) looks done until someone types new Bound(). Native bind still constructs: the bound this is ignored, prepended arguments still land first, and the instance’s [[Prototype]] is the original function’s .prototype, not some empty object you allocated by accident. The other miss is wrapping with an arrow. Arrows have no new.target and cannot be constructors, so new Bound() throws even when the target is a perfectly good constructor.
Write a standalone myBind(fn, thisArg, ...boundArgs). Putting it on Function.prototype is interview convention and mutates a shared builtin.
Key takeaways
- Ordinary calls use the bound
this. Latercall/applyon the bound function cannot replace it.- Bound arguments are prepended. The later call’s arguments are concatenated after them.
new Bound()ignores boundthisand constructs through the original function’s prototype.- An arrow target already has lexical
this. Bind still prepends args; it does not rebindthis.- The wrapper must be a
function, not an arrow, ornewis dead on arrival.
this, prepended args, and new on a bound function
bind returns a new function. It does not call the target. Spec shape, trimmed for a whiteboard: store the target, the bound this, and the bound argument list. On [[Call]], invoke the target with that this and boundArgs + args. On [[Construct]], ignore the bound this, forward the concatenated args, and let the original constructor allocate.
function myBind(fn, thisArg, ...boundArgs) {
if (typeof fn !== 'function') {
throw new TypeError('myBind called on a non-function');
}
function bound(...args) {
const allArgs = boundArgs.concat(args);
if (new.target) {
return Reflect.construct(fn, allArgs, new.target);
}
return fn.apply(thisArg, allArgs);
}
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
return bound;
}
function greet(greeting, mark) {
return greeting + ', ' + this.role + mark;
}
const boundHi = myBind(greet, { role: 'editor' }, 'Hi');
boundHi('!'); // 'Hi, editor!'
boundHi.call({ role: 'ignored' }, '?'); // still 'Hi, editor?'
Reflect.construct(fn, allArgs, new.target) is the construct path. When the caller wrote new bound(), new.target is bound. Wiring bound.prototype to Object.create(fn.prototype) is what makes the instance inherit the original methods. Native bind is an exotic function: it has no .prototype of its own and its [[Construct]] replaces newTarget with the original function when they match. The Object.create line is the interview stand-in for that prototype link.
If you skip Reflect.construct and only fn.apply(this, allArgs) under new, class constructors throw (Class constructor cannot be invoked without new), and a constructor that returns an object is easy to get wrong. Mention the new operator: if the constructor returns an object or a function, that value wins; primitives and null do not.
Call vs construct
| Call shape | this inside the target |
Arguments | Prototype of the result |
|---|---|---|---|
bound(x) |
bound thisArg |
prepended + x |
n/a (whatever the target returns) |
bound.call(other, x) |
still bound thisArg |
prepended + x |
n/a |
new bound(x) |
the new instance | prepended + x |
original fn.prototype |
| Arrow target, any of the above | lexical this of the arrow |
prepended + x |
new throws: arrows are not constructors |
function Widget(id, label) {
this.id = id;
this.label = label;
}
Widget.prototype.describe = function describe() {
return this.id + ':' + this.label;
};
const BoundWidget = myBind(Widget, { id: 'bound-this' }, 'w1');
const w = new BoundWidget('panel');
w.id; // 'w1' (prepended), not 'bound-this'
w.label; // 'panel'
w.describe(); // 'w1:panel'
Object.getPrototypeOf(w) === BoundWidget.prototype; // true in this polyfill
w instanceof Widget; // true
The object { id: 'bound-this' } never becomes this under new. That is the whole construct test. An ES5-only fallback uses this instanceof bound instead of new.target. It lies if someone does bound.call(existingInstance) where existingInstance was created by new bound. Prefer new.target and say so.
Arrows and partial args
bind on an arrow does not change this. It still returns a new function and still prepends arguments, which is why people use it as a poor man’s partial:
const add = (a, b, c) => a + b + c;
const addTen = myBind(add, { unused: true }, 10);
addTen(1, 2); // 13, thisArg ignored by the arrow
That is partial application, not this control. Real this work wants an ordinary function. See arrow functions and currying and partial application.
Footguns in a Function.prototype.bind polyfill
Returning an arrow wrapper fails new and fails prototype wiring. The bound function has to be constructable when the target is.
bound.prototype = fn.prototype (same reference, no Object.create) means BoundWidget.prototype.extra = 1 mutates Widget.prototype. Native code does not share that object that way. Object.create is the isolation.
Native bind also sets length to max(0, fn.length - boundArgs.length) and name to "bound " + fn.name. Skip those unless asked. Do not copy other own properties off fn; a bound function is not a clone.
bind is sticky for this on ordinary calls. bound.call(other) still uses the bound thisArg. The only thing that outranks it is new, which is this-binding rule 1.
Assigning Function.prototype.myBind = function (thisArg, ...boundArgs) { return myBind(this, thisArg, ...boundArgs); } is the prototype form. It mutates a builtin. Prefer the standalone function unless they insist.
Interview answer (out loud)
“bind returns a new function that stores thisArg and any leading arguments. On a normal call I apply the original with that this and the concatenated args. Later call and apply cannot override the bound this. On new I ignore the bound this, construct through the original function, and link the instance to the original prototype, usually with Object.create on the wrapper’s prototype or Reflect.construct. The wrapper is a regular function so new.target works. An arrow target keeps lexical this; bind still prepends args.”
Further reading
Related on this site
- call, apply, and bind
- this binding rules
- Function.prototype.call and apply polyfill
- Implement the new operator
- Arrow functions
- Polyfill questions playbook
- JavaScript interview learning path
Related guides
- Function.prototype.call and apply PolyfillPolyfill call and apply: set this for one invocation, spread an argument list, and explain why apply(null, array) is not the same as a spread.
- Array.filter PolyfillWrite filter that skips holes, respects thisArg, and never writes empty slots for rejected items. Contrast with map’s hole-preserving result.
- Array.find PolyfillWrite Array.prototype.find: first callback hit wins, holes are read as undefined, nothing matching returns undefined, and findIndex is the index twin.
- Array.flat PolyfillImplement flat(depth) with Infinity, holes, and non-array array-likes. Recursion vs stack, and why concat(spread) blows up on huge inputs.
- Array.forEach PolyfillImplement Array.prototype.forEach: visit present elements, skip holes, ignore the callback return, and honor thisArg without using native forEach.