Polyfill - call, apply & bind
Source: Dev.to
I’ll provide the polyfill code for each method with a brief explanation in separate sections. The focus is on simplicity and clarity so you can easily explain them during an interview.
1. Polyfill for call()
Code
// Polyfill for Function.prototype.call
if (!Function.prototype.customCall) {
Function.prototype.customCall = function (currentThis = {}, ...args) {
// Check if 'this' is a callable function
if (typeof this !== 'function') {
throw new Error(`${this} is not a function`);
}
// Attach the function to the provided context
currentThis.tempFn = this;
// Call the function with the arguments and return result
const result = currentThis.tempFn(...args);
// Clean up the temporary property
delete currentThis.tempFn;
return result;
};
}
// Example usage
const obj = { name: 'John' };
function greet(message) {
return `${message}, ${this.name}!`;
}
console.log(greet.customCall(obj, 'Hello')); // Outputs: Hello, John!
Explanation
- Purpose:
call()invokes a function with a specifiedthiscontext and individual arguments. It’s often used for function borrowing. - How it works: The polyfill temporarily attaches the function (
this) to the provided context (currentThis), calls it with the spread arguments (...args), then removes the temporary property to avoid polluting the object. - Key interview point:
call()passes arguments individually (unlikeapply()) and executes the function immediately. The error check ensuresthisis a function. - Limitation: This basic version doesn’t handle edge cases such as
null/undefinedcontexts, which in a full implementation would default to the global object.
2. Polyfill for apply()
Code
// Polyfill for Function.prototype.apply
if (!Function.prototype.customApply) {
Function.prototype.customApply = function (currentThis = {}, args = []) {
// Check if 'this' is a callable function
if (typeof this !== 'function') {
throw new Error(`${this} is not a function`);
}
// Check if args is an array
if (!Array.isArray(args)) {
throw new Error('Arguments must be an array');
}
// Attach the function to the provided context
currentThis.tempFn = this;
// Call the function with the array of arguments and return result
const result = currentThis.tempFn(...args);
// Clean up the temporary property
delete currentThis.tempFn;
return result;
};
}
// Example usage
const obj = { name: 'Jane' };
function greet(message, punctuation) {
return `${message}, ${this.name}${punctuation}`;
}
console.log(greet.customApply(obj, ['Hi', '!'])); // Outputs: Hi, Jane!
Explanation
- Purpose:
apply()works likecall(), but it accepts arguments as an array. This is handy when the number of arguments is dynamic. - How it works: After validating that
thisis a function and thatargsis an array, the polyfill temporarily attaches the function to the context, spreads the array into arguments, and then cleans up. - Key interview point: Emphasize that
apply()takes an array of arguments, making it useful for scenarios such as passing theargumentsobject or spreading an array of parameters. - Limitation: The implementation assumes
argsis always provided; a full polyfill would also handlenullor non‑array inputs gracefully.
3. Polyfill for bind()
Code
// Polyfill for Function.prototype.bind
if (!Function.prototype.customBind) {
Function.prototype.customBind = function (currentThis, ...boundArgs) {
// Check if 'this' is a callable function
if (typeof this !== 'function') {
throw new Error(`${this} is not a function`);
}
// Store the original function
const originalFn = this;
// Return a new function that can be invoked later
return function (...callArgs) {
// Call the original function with bound context and combined arguments
return originalFn.customApply(currentThis, boundArgs.concat(callArgs));
};
};
}
// Example usage
const obj = { name: 'Alice' };
function greet(message) {
return `${message}, ${this.name}!`;
}
const boundGreet = greet.customBind(obj, 'Hey');
console.log(boundGreet()); // Outputs: Hey, Alice!
console.log(boundGreet('Hello')); // Outputs: Hey, Alice! (ignores extra args)
Explanation
- Purpose:
bind()creates a new function with a fixedthiscontext and optionally preset arguments. It does not execute immediately; the returned function can be called later. - How it works: The polyfill captures the original function and any bound arguments, then returns a wrapper that merges those with any arguments supplied at call time. It uses
customApplyto invoke the original function with the fixed context. - Key interview point: Stress that
bind()is for delayed execution and permanent context binding (e.g., for event handlers). Explain how it merges preset and new arguments. - Limitation: This version relies on
customApplyfor simplicity and does not handle constructor usage (new) or other edge cases that a complete polyfill would need to address.
Unbound Calls
General Interview Talking Points
Why Polyfills?
Explain that polyfills like these ensure compatibility with older browsers that lack native support for call(), apply(), or bind().
- Performance – Native implementations are faster.
- Completeness – Polyfills fill gaps in environments where the methods are missing.
Common Theme
All three methods manipulate the this context, a core concept in JavaScript.
- Essential for functional‑programming patterns such as method borrowing.
Testing
- Test the polyfills across different environments (e.g., IE 11, older mobile browsers).
- In production, consider using a battle‑tested library like
core‑js, which covers many edge cases.
Browser Compatibility
- Use resources like caniuse.com or kangax’s ES6 compatibility table to verify support.
- Although
call,apply, andbindare widely supported today, writing polyfills remains a valuable learning exercise.
