Difficulty: Intermediate
Every function in JavaScript inherits three powerful methods from Function.prototype: call, apply, and bind. These methods give you explicit control over the `this` context when invoking a function. While they all serve a similar purpose -- setting the value of `this` -- they differ in how arguments are passed and whether the function is invoked immediately or returned as a new function.
Function.prototype.call() invokes the function immediately with a specified `this` value and individual arguments passed one after another. The first argument to call() becomes `this` inside the function, and subsequent arguments are passed as the function's parameters. This is useful when you know the exact arguments you want to pass and want to invoke the function immediately with a different context.
Function.prototype.apply() works almost identically to call(), but with one key difference: instead of passing arguments individually, you pass them as an array (or array-like object). This is particularly useful when you have a dynamic list of arguments, such as when forwarding arguments from one function to another. Before the spread operator existed, apply() was the standard way to pass an array of arguments to a function. For example, Math.max.apply(null, [1, 2, 3]) was the way to find the maximum of an array before Math.max(...arr) was possible.
Function.prototype.bind() is different from call and apply because it does not invoke the function immediately. Instead, it returns a new function with `this` permanently bound to the specified value. Any additional arguments passed to bind are prepended to the arguments of the new function, enabling a technique called partial application. The bound function's `this` cannot be changed by subsequent call, apply, or bind calls (though the `new` operator can override it). This makes bind ideal for creating callbacks that need a fixed context.
These methods have several practical use cases. Method borrowing allows you to use methods from one object on another, like using Array.prototype.slice on arguments objects or NodeLists. Partial application with bind lets you create specialized versions of general functions by pre-filling some arguments. In class-based components and event handlers, bind is commonly used to ensure methods retain their correct `this` context when passed as callbacks.
Understanding how to implement polyfills for call, apply, and bind is a common interview requirement. The basic idea behind a call polyfill is to temporarily attach the function as a method of the target object, invoke it, and then remove it. This leverages implicit binding to set `this` correctly. The bind polyfill returns a new function that uses apply internally to invoke the original function with the correct context.
function introduce(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
// call -- invoke immediately, args passed individually
introduce.call(person, 'Hello', '!');
// apply -- invoke immediately, args passed as array
introduce.apply(person, ['Hi', '.']);
// bind -- returns a new function, does NOT invoke
const boundIntroduce = introduce.bind(person, 'Hey');
boundIntroduce('!!'); // 'Hey' was pre-filled by bind
// bind with all arguments pre-filled (full partial application)
const fullyBound = introduce.bind(person, 'Greetings', '~');
fullyBound(); // No arguments needed
call passes arguments individually, apply passes them as an array, and bind returns a new function. bind can also pre-fill arguments (partial application), so the returned function needs fewer arguments.
// Borrowing Array methods for array-like objects
function sumArguments() {
// arguments is array-like but not an array
// Borrow Array.prototype.reduce to sum values
const sum = Array.prototype.reduce.call(
arguments,
(acc, val) => acc + val,
0
);
return sum;
}
console.log(sumArguments(1, 2, 3, 4, 5)); // 15
// Borrowing methods between objects
const logger = {
prefix: '[LOG]',
log(message) {
console.log(`${this.prefix} ${message}`);
}
};
const errorLogger = { prefix: '[ERROR]' };
const warnLogger = { prefix: '[WARN]' };
// Borrow logger's log method with different contexts
logger.log.call(errorLogger, 'Something failed');
logger.log.call(warnLogger, 'Deprecated API used');
logger.log('Normal message');
Method borrowing uses call/apply to invoke a method from one object in the context of another. This is especially useful for array-like objects (arguments, NodeLists) that need array methods but are not actual arrays.
// General-purpose tax calculator
function calculatePrice(taxRate, price) {
const tax = price * taxRate;
return {
price,
tax: tax.toFixed(2),
total: (price + tax).toFixed(2)
};
}
// Create specialized versions using bind
const calcUSPrice = calculatePrice.bind(null, 0.0725); // 7.25% tax
const calcUKPrice = calculatePrice.bind(null, 0.20); // 20% VAT
const calcTaxFree = calculatePrice.bind(null, 0); // No tax
console.log('US:', calcUSPrice(100));
console.log('UK:', calcUKPrice(100));
console.log('Free:', calcTaxFree(100));
// Partial application for event handling
function handleClick(action, event) {
console.log(`Action: ${action}, Target: ${event.target}`);
}
// Pre-fill the action, event will be passed by the handler
const handleSave = handleClick.bind(null, 'save');
const handleDelete = handleClick.bind(null, 'delete');
// Usage: button.addEventListener('click', handleSave);
bind with null as the first argument ignores the this context and just pre-fills arguments. This creates specialized functions from general-purpose ones, reducing repetition and improving readability.
// Simplified polyfill for Function.prototype.call
Function.prototype.myCall = function(context, ...args) {
// If context is null/undefined, default to globalThis
context = context ?? globalThis;
// Use a unique symbol to avoid property name collisions
const fnKey = Symbol('fn');
// Temporarily attach this function as a method
context[fnKey] = this;
// Invoke with implicit binding (context.fn())
const result = context[fnKey](...args);
// Clean up
delete context[fnKey];
return result;
};
// Simplified polyfill for Function.prototype.bind
Function.prototype.myBind = function(context, ...boundArgs) {
const originalFn = this;
return function(...callArgs) {
return originalFn.apply(context, [...boundArgs, ...callArgs]);
};
};
// Test the polyfills
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const user = { name: 'Alice' };
console.log(greet.myCall(user, 'Hello'));
const boundGreet = greet.myBind(user, 'Hi');
console.log(boundGreet());
The call polyfill works by temporarily attaching the function as a method of the context object, invoking it (which triggers implicit binding), and then cleaning up. The bind polyfill returns a new function that uses apply to invoke the original function with the correct context and merged arguments.
Function.prototype.call, Function.prototype.apply, Function.prototype.bind, Method Borrowing, Partial Application