5.5 Call, Apply, and Bind
Overview
JavaScript provides three methods to explicitly control the this context of a function: call(), apply(), and bind(). These methods allow you to invoke functions with a specific context or create new functions with a permanently bound context.
Table of Contents
- Understanding
thisContext - The call() Method
- The apply() Method
- call vs apply
- The bind() Method
- Practical Use Cases
- Method Borrowing
- Partial Application with bind
- Common Patterns
- Best Practices
Understanding this Context
The value of this depends on how a function is called.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β How 'this' is Determined β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Call Type 'this' Value β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β Regular function call global/undefined β
β Method call (obj.method()) obj β
β Constructor (new Func()) new instance β
β call/apply/bind explicitly set β
β Arrow function inherited (lexical) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Problem
const user = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
},
};
user.greet(); // "Hello, Alice"
const greet = user.greet;
greet(); // "Hello, undefined" - 'this' is lost!
The call() Method
Invokes a function with a specified this value and individual arguments.
Syntax
function.call(thisArg, arg1, arg2, ...)
Basic Example
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const user1 = { name: 'Alice' };
const user2 = { name: 'Bob' };
greet.call(user1, 'Hello'); // "Hello, Alice!"
greet.call(user2, 'Hi'); // "Hi, Bob!"
With Multiple Arguments
function introduce(greeting, profession) {
console.log(`${greeting}, I'm ${this.name}, a ${profession}.`);
}
const person = { name: 'Alice' };
introduce.call(person, 'Hello', 'developer');
// "Hello, I'm Alice, a developer."
Using call to Borrow Methods
const numbers = [1, 2, 3, 4, 5];
// Borrow max from Math
const max = Math.max.call(null, ...numbers);
console.log(max); // 5
// Convert array-like to array
function example() {
const args = Array.prototype.slice.call(arguments);
console.log(args);
}
example(1, 2, 3); // [1, 2, 3]
The apply() Method
Similar to call(), but takes arguments as an array.
Syntax
function.apply(thisArg, [argsArray])
Basic Example
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const user = { name: 'Alice' };
greet.apply(user, ['Hello', '!']);
// "Hello, Alice!"
With Math Methods
const numbers = [5, 6, 2, 3, 7];
// Find max/min without spread
const max = Math.max.apply(null, numbers);
const min = Math.min.apply(null, numbers);
console.log(max); // 7
console.log(min); // 2
Dynamic Argument Passing
function log(message, ...args) {
console.log(`[${this.level}] ${message}`, ...args);
}
const logger = { level: 'INFO' };
const args = ['User %s logged in', 'Alice'];
log.apply(logger, args);
// "[INFO] User Alice logged in"
call vs apply
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β call() vs apply() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β call(context, arg1, arg2, arg3) // C = Comma β
β apply(context, [arg1, arg2, arg3]) // A = Array β
β β
β // Identical results: β
β fn.call(obj, 1, 2, 3); β
β fn.apply(obj, [1, 2, 3]); β
β β
β // Modern alternative with spread: β
β fn.call(obj, ...args); β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Choosing Between Them
Use call() | Use apply() |
|---|---|
| Known number of args | Dynamic array of args |
| Arguments are separate | Arguments in array |
| More readable for few args | Legacy/array operations |
// When you know the arguments
greet.call(user, 'Hello', '!');
// When arguments are in an array
const args = ['Hello', '!'];
greet.apply(user, args);
// Modern: use spread with call
greet.call(user, ...args);
The bind() Method
Creates a new function with this permanently bound.
Syntax
const boundFn = function.bind(thisArg, arg1, arg2, ...)
Basic Example
const user = {
name: 'Alice',
greet() {
console.log('Hello, ' + this.name);
},
};
const greet = user.greet;
greet(); // "Hello, undefined"
const boundGreet = user.greet.bind(user);
boundGreet(); // "Hello, Alice"
Key Difference from call/apply
// call/apply invoke immediately
greet.call(user); // Invoked now
// bind returns a new function
const bound = greet.bind(user); // Returns function
bound(); // Invoked when called
Bind with Preset Arguments
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const user = { name: 'Alice' };
// Bind with some arguments preset
const sayHello = greet.bind(user, 'Hello');
sayHello('!'); // "Hello, Alice!"
sayHello('?'); // "Hello, Alice?"
// Bind with all arguments preset
const fixedGreet = greet.bind(user, 'Hi', '!');
fixedGreet(); // "Hi, Alice!"
Practical Use Cases
1. Event Handlers
class Button {
constructor(label) {
this.label = label;
}
handleClick() {
console.log(`${this.label} clicked`);
}
}
const btn = new Button('Submit');
// Without bind - 'this' is the event target
button.addEventListener('click', btn.handleClick); // Wrong this
// With bind - 'this' is the Button instance
button.addEventListener('click', btn.handleClick.bind(btn));
2. setTimeout/setInterval
const counter = {
count: 0,
start() {
// Without bind - 'this' would be undefined
setInterval(
function () {
this.count++;
console.log(this.count);
}.bind(this),
1000
);
},
};
// Alternative: arrow function
const counter2 = {
count: 0,
start() {
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000);
},
};
3. Callbacks
class UserService {
constructor() {
this.users = [];
}
fetchUsers() {
fetch('/api/users')
.then(this.handleResponse.bind(this))
.catch(this.handleError.bind(this));
}
handleResponse(response) {
// 'this' refers to UserService
this.users = response.data;
}
handleError(error) {
console.log('Error in', this.constructor.name);
}
}
Method Borrowing
Use methods from one object on another.
Borrowing Array Methods
// arguments is array-like but not an array
function sum() {
// Borrow forEach from Array.prototype
let total = 0;
Array.prototype.forEach.call(arguments, function (n) {
total += n;
});
return total;
}
sum(1, 2, 3, 4, 5); // 15
Common Array Method Borrowing
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// slice to convert to array
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b', 'c']
// join
const joined = Array.prototype.join.call(arrayLike, '-');
console.log(joined); // "a-b-c"
// Modern alternative
const arr2 = Array.from(arrayLike);
const arr3 = [...arrayLike]; // If iterable
Borrowing Object Methods
const obj = { a: 1, b: 2 };
// Borrow hasOwnProperty safely
const hasOwn = Object.prototype.hasOwnProperty;
console.log(hasOwn.call(obj, 'a')); // true
console.log(hasOwn.call(obj, 'toString')); // false
// Modern alternative
console.log(Object.hasOwn(obj, 'a')); // true
Borrowing String Methods
const str = 'Hello';
// Use array methods on string
const reversed = Array.prototype.reverse.call([...str]).join('');
console.log(reversed); // "olleH"
Partial Application with bind
Pre-fill some arguments of a function.
Basic Partial Application
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
With Multiple Arguments
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
const sayHello = greet.bind(null, 'Hello');
console.log(sayHello('Alice', '!')); // "Hello, Alice!"
const sayHelloToAlice = greet.bind(null, 'Hello', 'Alice');
console.log(sayHelloToAlice('!')); // "Hello, Alice!"
Practical Example: Logger
function log(level, message, ...data) {
console.log(`[${level}] ${message}`, ...data);
}
const info = log.bind(null, 'INFO');
const error = log.bind(null, 'ERROR');
const debug = log.bind(null, 'DEBUG');
info('User logged in', { userId: 123 });
error('Connection failed', { host: 'localhost' });
Common Patterns
1. Constructor Binding Pattern
class Component {
constructor() {
this.state = { count: 0 };
// Bind methods in constructor
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick() {
this.state.count++;
}
handleChange(value) {
console.log(this.state.count, value);
}
}
2. Function Composition with call
function compose(...fns) {
return function (value) {
return fns.reduceRight((acc, fn) => fn.call(this, acc), value);
};
}
3. Mixin Pattern with apply
const eventMixin = {
on(event, handler) {
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(handler);
},
emit(event, ...args) {
if (this._events?.[event]) {
this._events[event].forEach((fn) => fn.apply(this, args));
}
},
};
// Apply mixin to any object
Object.assign(user, eventMixin);
user.on('login', function () {
console.log(this.name + ' logged in');
});
user.emit('login');
4. Borrowing Constructor
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name); // Borrow Animal's constructor
this.breed = breed;
}
const dog = new Dog('Rex', 'German Shepherd');
console.log(dog.name, dog.breed); // "Rex" "German Shepherd"
Best Practices
1. Use Arrow Functions for Simple Callbacks
// β Verbose with bind
button.addEventListener('click', this.handleClick.bind(this));
// β
Simpler with arrow function
button.addEventListener('click', () => this.handleClick());
2. Bind in Constructor for Class Methods
class Component {
constructor() {
// β
Bind once in constructor
this.handleClick = this.handleClick.bind(this);
}
}
// β Don't bind in render/every call
render() {
button.onclick = this.handleClick.bind(this); // New function each time
}
3. Use null for thisArg When Not Needed
// When 'this' doesn't matter
const max = Math.max.apply(null, numbers);
const double = multiply.bind(null, 2);
4. Prefer Spread Over apply for Arrays
// β Old way
Math.max.apply(null, numbers);
// β
Modern way
Math.max(...numbers);
5. Be Careful with Multiple Binds
function greet() {
console.log(this.name);
}
const bound1 = greet.bind({ name: 'Alice' });
const bound2 = bound1.bind({ name: 'Bob' }); // Ignored!
bound2(); // "Alice" - first bind wins
Summary
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β call, apply, bind Quick Reference β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β call(context, arg1, arg2) β
β β’ Invokes immediately β
β β’ Arguments as comma-separated list β
β β
β apply(context, [args]) β
β β’ Invokes immediately β
β β’ Arguments as array β
β β
β bind(context, arg1, arg2) β
β β’ Returns new bound function β
β β’ Can preset arguments (partial application) β
β β’ 'this' permanently bound β
β β
β Common Uses: β
β β’ Method borrowing β
β β’ Event handler binding β
β β’ Callback context preservation β
β β’ Partial application β
β β’ Constructor borrowing β
β β
β Memory Tip: β
β β’ Call = Comma (individual arguments) β
β β’ Apply = Array (arguments in array) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Next Steps
- Practice with the examples in
examples.js - Complete the exercises in
exercises.js - Learn about closures and scope
- Explore prototypes and inheritance