Function.prototype.call Polyfill
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
JavaScript · Functions · this — Pratik Rai ·
Implement Function.prototype.apply from scratch — identical to call except arguments arrive as a single array.
Function.prototype.apply invokes a function with an explicit this and an array of arguments. It is call's sibling, differing only in how arguments arrive — but that one difference is what made it indispensable for two decades of JavaScript, and it carries a hard engine limit that still bites people today.
apply is the shortest of the three context methods to write, so on its own it is a warm-up. Its value is in what it explains: before the spread operator existed, apply was the only way to pass a runtime-length list of arguments to a function. Nearly every clever pre-ES6 idiom you will find in old code — Math.max.apply(null, arr), [].concat.apply([], arrays), array-like conversion — exists because of it.
JSfile.javascript1function introduce(greeting, punctuation) { 2 return `${greeting}, I am ${this.name}${punctuation}`; 3} 4 5introduce.apply({ name: 'Ada' }, ['Hello', '!']); // "Hello, I am Ada!"
The specification requires:
TypeError if the target is not callable.null or undefined for thisArg defaults to the global object in sloppy mode; primitives are boxed.null or undefined, meaning "no arguments" — it is optional in a way call's rest arguments never need to be.{0: 'a', length: 1} works.That third point is the one people forget. fn.apply(obj) with no second argument is legal and calls fn with zero arguments.
The mechanism is identical to call — temporarily attach the function to the context object so that a method call sets this, then clean up:
JSfile.javascript1Function.prototype.myApply = function (thisArg, argArray) { 2 if (typeof this !== 'function') { 3 throw new TypeError('myApply must be called on a function'); 4 } 5 6 const context = 7 thisArg === null || thisArg === undefined ? globalThis : Object(thisArg); 8 9 // `apply` accepts null/undefined to mean "no arguments". 10 const args = 11 argArray === null || argArray === undefined ? [] : Array.from(argArray); 12 13 const key = Symbol('fn'); 14 context[key] = this; 15 16 try { 17 return context[key](...args); 18 } finally { 19 delete context[key]; 20 } 21};
Array.from handles the array-like requirement in one step and also accepts iterables, which is slightly more permissive than the spec but rarely a problem in practice.
The Symbol key and the try/finally matter for the same reasons they do in call: a string key can collide with a property the caller already owns, and a function that throws would otherwise leave the temporary property attached permanently.
This is apply's one genuinely dangerous property. Arguments are placed on the stack, and every engine caps how many it will accept — roughly 65,535 in older engines, higher but still finite in modern V8. Exceed it and you get RangeError: Maximum call stack size exceeded.
JSfile.javascript1const big = new Array(500000).fill(1); 2Math.max.apply(null, big); // RangeError 3Math.max(...big); // RangeError — spread has the same limit
The spread operator does not save you; it compiles to the same mechanism. For large arrays, use a reducer or chunk the input:
JSfile.javascript1big.reduce((max, n) => (n > max ? n : max), -Infinity); // safe at any size
This trips people up precisely because Math.max.apply(null, arr) is the textbook example of apply, and it works fine right up until your array gets big enough in production.
Math.max.apply(null, arr) — find the maximum of an array. Superseded by Math.max(...arr), with the same size caveat.
[].concat.apply([], arrays) — flatten one level. Superseded by arr.flat().
Array.prototype.slice.call(arguments) — convert an array-like to a real array. Superseded by Array.from and rest parameters.
Recognising these matters when reading older code, and each one is a small lesson in why the modern syntax exists.
"When would you use apply over call today?" Honestly, rarely — spread covers most cases more readably. It still wins when you already have an array in hand and want to avoid the spread's allocation, and when forwarding arguments verbatim in code that cannot use rest parameters.
"What is the difference between call, apply and bind?" call and apply invoke immediately and differ only in argument shape; bind returns a new function and invokes nothing. The mnemonic: apply takes an array, call takes a comma-separated list.
apply is call with arguments passed as an array-like.null and undefined both mean "no arguments".Symbol key and try/finally, same as call.apply idioms have cleaner modern replacements.Goal: Implement myApply. Reuse your call logic — the only new piece is handling the args array.
Continue learning with these related challenges
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
JavaScript · Functions · this — Pratik Rai ·
Implement Function.prototype.bind from scratch, including partial application and the new-operator edge case that almost nobody gets right.
JavaScript · Functions · Polyfills — Pratik Rai ·
Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.
JavaScript · Arrays · Polyfills — Pratik Rai ·
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
JavaScript · Functions · this
Pratik Rai ·
Implement Function.prototype.bind from scratch, including partial application and the new-operator edge case that almost nobody gets right.
JavaScript · Functions · Polyfills
Pratik Rai ·
Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.
JavaScript · Arrays · Polyfills
Pratik Rai ·