Implement Polyfills for bind, call, apply, and compose
Problem
Implement your own polyfills for Function.prototype.call, Function.prototype.apply, and Function.prototype.bind, plus a compose utility — demonstrating how this binding and function composition actually work.
Requirements
Function.prototype.myCall(thisArg, ...args) -> resultFunction.prototype.myApply(thisArg, argsArray) -> resultFunction.prototype.myBind(thisArg, ...boundArgs) -> Function— supports partial application, and works withnewcompose(...fns) -> Functionwherecompose(f, g, h)(x) === f(g(h(x)))
Core design
- call / apply: temporarily attach the function as a property of the
thisArgobject, then invoke it as a method — method invocation is precisely what setsthisin JavaScript, so this exploits the language's own binding rule rather than fighting it. Use aSymbol()key so the temporary property can never collide with an existing one, capture the result,deletethe property, and return.callspreads its arguments;applytakes them as an array — that is the only difference between the two. - Handle
thisArgbeingnull/undefined(non-strict mode substitutes globalThis) and primitives (boxed to their object wrapper). - bind: return a new function that, when invoked, calls the original with the bound
thisand[...boundArgs, ...callArgs]concatenated — this argument concatenation is what gives partial application. - The
newcase is the part most implementations miss: when the bound function is used as a constructor, the boundthismust be ignored and the freshly constructed object used instead. Detect it withthis instanceof BoundFunction, and set the prototype chain so instances stillinstanceofthe original. - compose:
fns.reduceRight((acc, fn) => (...args) => fn(acc(...args))), threading each output into the next. Right-to-left is what makes it read like mathematical composition;pipeis the same fold in the opposite direction.
Discussion points
- Why
call/applyneed the temporary-property trick at all:thisin JavaScript is set by the call site, not by lexical scope, so there is no way to set it other than invoking the function as a method of the target. - Arrow functions have no own
thisand no[[Construct]]— they ignore all three polyfills' binding entirely and throw withnew. A correct explanation names this. - Trade-off:
Reflect.applygives the same behaviour natively and avoids mutating the target object at all; the property-attachment approach exists precisely because polyfills predate it. - Double-binding: a bound function cannot be re-bound — the first
bindwins, since the inner call already fixesthis. Falls out of the implementation but surprises people. composeshould handle the zero-function case (identity) and only pass multiple arguments to the innermost function — the rest receive exactly one value.- Edge cases: non-callable receiver (throw
TypeError), preservinglengthandnameon the bound function, and that a bound function'slengthis the original's minus the bound args.
asked …