Docs
README
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