Polyfill Function.prototype.bind — and explain call / apply / bind
`call(thisArg, ...args)` invokes immediately with a given `this`. `apply(thisArg, argsArray)` is the same but with args as an array. `bind(thisArg, ...partials)` returns a new function with `this` permanently set. The polyfill closes over `thisArg` + partials and uses `apply` (or `call`) internally — plus a `new.target` check so the bound function still works as a constructor.
call, apply, and bind all set what this refers to inside a function. They differ in when and how.
| invokes? | args | |
|---|---|---|
fn.call(ctx, a, b) | now | spread |
fn.apply(ctx, [a, b]) | now | array |
fn.bind(ctx, a) | returns a new fn | partial-applied |
call and apply are interchangeable in practice — pick whichever matches how you have the args. bind is different: it produces a new function with this (and optionally leading args) locked in.
The polyfill.
Function.prototype.myBind = function (thisArg, ...partials) {
const target = this;
if (typeof target !== "function") {
throw new TypeError("Bind must be called on a function");
}
function bound(...args) {
// If called with `new`, ignore thisArg — `this` is the fresh instance.
const isCtor = this instanceof bound;
return target.apply(isCtor ? this : thisArg, [...partials, ...args]);
}
// Preserve prototype chain so `new boundFn()` still produces correct instances.
if (target.prototype) {
bound.prototype = Object.create(target.prototype);
}
return bound;
};Why the new.target / instanceof check matters. The spec says: a bound function used as a constructor ignores the bound thisArg. Without that check, new BoundFn() would override the fresh instance with the bound context — instances would silently share state. Most candidate polyfills miss this.
Why preserve prototype. new boundFn() should produce something that's instanceof OriginalCtor. Linking bound.prototype to the target's prototype keeps the chain.
Partial application. bind takes args before the new function is called and prepends them on invocation:
const add = (a, b) => a + b;
const inc = add.bind(null, 1);
inc(5); // 6This is partial application, not currying. Currying produces a chain of unary functions; bind fixes a known prefix and accepts the rest.
Common gotchas.
bindreturns a new function each time — don'taddEventListener(..., fn.bind(this))then try toremoveEventListenerwith a freshly bound version; the reference differs.- Arrow functions ignore
bindforthis— they capture lexically.bindcan still set partial args, butthisArgis silently ignored. call/applywithnullorundefinedin sloppy mode coercesthisto the global object; in strict mode it staysnull/undefined.
When to reach for which in real code. Almost never — arrow functions and lexical this have made manual bind mostly obsolete. The two remaining uses: (1) borrowing methods (Array.prototype.slice.call(arguments), though spread does this now) and (2) partial application without a helper library.
Follow-up questions
- •Why is the `new.target` check necessary in the bind polyfill?
- •Difference between partial application and currying.
- •How does `this` differ inside arrow functions vs regular functions?
- •Polyfill `call` and `apply` from scratch — what's the minimum needed?
Common mistakes
- •Forgetting the `new`-as-constructor case — bound function used with `new` should ignore `thisArg`.
- •Not preserving the prototype chain so `instanceof` breaks on bound constructors.
- •Using `bind` on arrow functions and expecting `this` to change.
- •Re-binding inside render and breaking referential equality for memoized children.
Performance considerations
- •Each `.bind()` allocates a new function object — avoid in hot paths and tight loops.
- •Arrow functions in class fields are typically faster than `bind` in constructor.
Edge cases
- •Calling `bind` on something that isn't a function — throw TypeError.
- •Binding a generator or async function — the polyfill should preserve that behavior (modern `Function.prototype.bind` does).
- •Binding twice — the second `bind`'s `thisArg` is silently ignored (the first one wins).
Real-world examples
- •Pre-class React class methods: `this.handleClick = this.handleClick.bind(this)`.
- •`Array.from(arrayLike)` replaces `Array.prototype.slice.call(arrayLike)` — both relied on `call` to borrow.