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) -> result
  • Function.prototype.myApply(thisArg, argsArray) -> result
  • Function.prototype.myBind(thisArg, ...boundArgs) -> Function — supports partial application, and works with new
  • compose(...fns) -> Function where compose(f, g, h)(x) === f(g(h(x)))

Core design

  • call / apply: temporarily attach the function as a property of the thisArg object, then invoke it as a method — method invocation is precisely what sets this in JavaScript, so this exploits the language's own binding rule rather than fighting it. Use a Symbol() key so the temporary property can never collide with an existing one, capture the result, delete the property, and return. call spreads its arguments; apply takes them as an array — that is the only difference between the two.
  • Handle thisArg being null/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 this and [...boundArgs, ...callArgs] concatenated — this argument concatenation is what gives partial application.
  • The new case is the part most implementations miss: when the bound function is used as a constructor, the bound this must be ignored and the freshly constructed object used instead. Detect it with this instanceof BoundFunction, and set the prototype chain so instances still instanceof the 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; pipe is the same fold in the opposite direction.

Discussion points

  • Why call/apply need the temporary-property trick at all: this in 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 this and no [[Construct]] — they ignore all three polyfills' binding entirely and throw with new. A correct explanation names this.
  • Trade-off: Reflect.apply gives 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 bind wins, since the inner call already fixes this. Falls out of the implementation but surprises people.
  • compose should 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), preserving length and name on the bound function, and that a bound function's length is the original's minus the bound args.
asked …
LeaderboardSalaryAccount