Call, Bind, Apply
Understanding how to manipulate function context (this)
What are Call, Bind, and Apply?
These are methods that allow you to explicitly set the 'this' context of a function. They're useful for borrowing methods and controlling function execution context.
Try Call, Bind, Apply
Code Editor
Console Output
Click "Run" to execute your code...
Example 1: call() Method
// call() invokes function with specified 'this' and arguments
const person1 = {
name: 'John',
age: 30
};
const person2 = {
name: 'Jane',
age: 25
};
function greet(greeting, punctuation) {
return `${greeting}, I'm ${this.name} and I'm ${this.age} years old${punctuation}`;
}
// Using call()
console.log(greet.call(person1, 'Hello', '!'));
// Hello, I'm John and I'm 30 years old!
console.log(greet.call(person2, 'Hi', '.'));
// Hi, I'm Jane and I'm 25 years old.
// Borrowing methods
const obj1 = {
name: 'Object 1',
display: function() {
console.log(`Name: ${this.name}`);
}
};
const obj2 = {
name: 'Object 2'
};
// obj2 doesn't have display method, but can borrow it
obj1.display.call(obj2); // Name: Object 2
// Practical example: Array-like objects
function printArguments() {
// arguments is array-like but not an array
Array.prototype.forEach.call(arguments, (arg, index) => {
console.log(`Arg ${index}: ${arg}`);
});
}
printArguments('a', 'b', 'c');
// Arg 0: a
// Arg 1: b
// Arg 2: cExample 2: apply() Method
// apply() is like call(), but takes arguments as an array
const person = {
name: 'Alice',
age: 28
};
function introduce(greeting, hobby, location) {
return `${greeting}! I'm ${this.name}, ${this.age}. I love ${hobby} in ${location}.`;
}
// Using apply() with array of arguments
const args = ['Hi', 'coding', 'New York'];
console.log(introduce.apply(person, args));
// Hi! I'm Alice, 28. I love coding in New York.
// Compare with call()
console.log(introduce.call(person, 'Hello', 'reading', 'Boston'));
// Practical use: Math.max with array
const numbers = [5, 6, 2, 3, 7, 9, 1];
// Math.max doesn't accept arrays
// console.log(Math.max(numbers)); // NaN
// Use apply to spread array as arguments
console.log(Math.max.apply(null, numbers)); // 9
console.log(Math.min.apply(null, numbers)); // 1
// Modern alternative: spread operator
console.log(Math.max(...numbers)); // 9
// Concatenating arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// Using apply
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
// Modern alternative
const arr3 = [7, 8, 9];
const arr4 = [10, 11, 12];
arr3.push(...arr4);
console.log(arr3); // [7, 8, 9, 10, 11, 12]Example 3: bind() Method
// bind() creates a new function with fixed 'this' value
const person = {
name: 'Bob',
age: 35,
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
// Regular method call
person.greet(); // Hello, I'm Bob
// Problem: losing 'this' context
const greetFunction = person.greet;
// greetFunction(); // TypeError or undefined (this is not person)
// Solution: bind()
const boundGreet = person.greet.bind(person);
boundGreet(); // Hello, I'm Bob
// Partial application with bind()
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
console.log(double(10)); // 20
const triple = multiply.bind(null, 3);
console.log(triple(5)); // 15
// Event listeners example
const button = {
text: 'Click me',
handleClick: function() {
console.log(`Button text: ${this.text}`);
}
};
// In browser:
// document.getElementById('btn').addEventListener('click', button.handleClick);
// Would lose 'this' context
// Fix with bind:
// document.getElementById('btn').addEventListener('click', button.handleClick.bind(button));
// React class component example pattern
class Counter {
constructor() {
this.count = 0;
// Bind in constructor
this.increment = this.increment.bind(this);
}
increment() {
this.count++;
console.log(`Count: ${this.count}`);
}
}
const counter = new Counter();
const inc = counter.increment;
inc(); // Count: 1 (works because of bind!)Example 4: Comparing call, apply, and bind
const obj = { value: 42 };
function showValue(prefix, suffix) {
return `${prefix} ${this.value} ${suffix}`;
}
// call() - immediate invocation, individual arguments
console.log(showValue.call(obj, 'Value:', '!'));
// Value: 42 !
// apply() - immediate invocation, array of arguments
console.log(showValue.apply(obj, ['Value:', '!']));
// Value: 42 !
// bind() - returns new function, can be called later
const boundShow = showValue.bind(obj, 'Value:');
console.log(boundShow('!'));
// Value: 42 !
// Key differences:
// - call & apply: invoke immediately
// - bind: returns new function
// - call: arguments listed individually
// - apply: arguments as array
// - bind: can preset arguments (partial application)
// Chaining example
function log(level, message) {
console.log(`[${level}] ${this.name}: ${message}`);
}
const logger = { name: 'MyLogger' };
// Create specialized loggers
const info = log.bind(logger, 'INFO');
const error = log.bind(logger, 'ERROR');
const warn = log.bind(logger, 'WARN');
info('Application started'); // [INFO] MyLogger: Application started
error('Something went wrong'); // [ERROR] MyLogger: Something went wrong
warn('This is a warning'); // [WARN] MyLogger: This is a warningExample 5: Practical Use Cases
// Use Case 1: Function borrowing
const person1 = {
firstName: 'John',
lastName: 'Doe',
getFullName: function() {
return `${this.firstName} ${this.lastName}`;
}
};
const person2 = {
firstName: 'Jane',
lastName: 'Smith'
};
console.log(person1.getFullName.call(person2)); // Jane Smith
// Use Case 2: Converting array-like to array
function toArray() {
return Array.prototype.slice.call(arguments);
}
const arr = toArray(1, 2, 3, 4, 5);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(Array.isArray(arr)); // true
// Modern alternative:
function toArrayModern() {
return Array.from(arguments);
// or [...arguments]
}
// Use Case 3: Curry functions
function add(a, b, c) {
return a + b + c;
}
const add5 = add.bind(null, 5);
const add5And10 = add5.bind(null, 10);
console.log(add5And10(15)); // 30 (5 + 10 + 15)
console.log(add5(10, 15)); // 30 (5 + 10 + 15)
// Use Case 4: Timing and debouncing
const processor = {
name: 'DataProcessor',
process: function(data) {
console.log(`${this.name} processing: ${data}`);
}
};
// Without bind, setTimeout loses context
// setTimeout(processor.process, 1000); // Error
// With bind
setTimeout(processor.process.bind(processor, 'important data'), 1000);
// DataProcessor processing: important data
// Use Case 5: Polyfill for bind (understanding how it works)
if (!Function.prototype.customBind) {
Function.prototype.customBind = function(context, ...args) {
const fn = this;
return function(...newArgs) {
return fn.apply(context, [...args, ...newArgs]);
};
};
}
function test(a, b) {
return `${this.value} - ${a} - ${b}`;
}
const boundTest = test.customBind({ value: 'Test' }, 'arg1');
console.log(boundTest('arg2')); // Test - arg1 - arg2Key Points
- call() and apply() invoke function immediately with specified 'this'
- bind() returns a new function with fixed 'this' value
- call() takes arguments individually: func.call(thisArg, arg1, arg2)
- apply() takes arguments as array: func.apply(thisArg, [arg1, arg2])
- bind() can preset arguments (partial application)
- Useful for method borrowing and preserving context
- Arrow functions cannot be bound (they lexically bind 'this')