Skip to Content
UtilsCall, bind, apply

call, bind, and apply in JavaScript

Understanding call, bind, and apply is essential for mastering how JavaScript handles function context (this). These tools allow you to control the execution context of functions, borrow behavior across objects, and preserve method bindings in callbacks and asynchronous code.

This guide includes:

  • Differences between call, apply, and bind
  • Use cases for method borrowing, decorators, and delayed execution
  • A custom bind implementation
  • Behavior of arrow functions with this

Losing and Rebinding Context

This example demonstrates what happens when a method loses its context (this) and how to recover it using call() and bind():

  • Calling counter.add(1) works as expected — this refers to counter.
  • When passing counter.add directly to another function (operate), it loses context and throws an error.
  • Using call() explicitly sets the context.
  • Using bind() returns a new function with this permanently bound to counter.
Losing and Rebinding Context
const counter = { value: 10, add(num) { this.value += num; return this.value; }, }; function operate(method, num) { return method(num); } function operateWithCall(method, num) { return method.call(counter, num); } console.log(counter.add(1)); // 11 console.log(operateWithCall(counter.add, 1)); // 12 console.log(operate(counter.add.bind(counter), 1)); // 13 try { console.log(operate(counter.add, 1)); } catch (e) { console.error(e.message); // Cannot read properties of undefined (reading 'value') }

Using call and apply to Change Context

This example shows how call() and apply() can be used to invoke a method with a different this context:

  • objA.print.call(objB) executes objA.print with objB as this, so it logs "I am from objB".
  • apply() works similarly, but takes arguments as an array.
call and apply
const objA = { data: "I am from objA", print() { console.log(this.data); }, }; const objB = { data: "I am from objB", }; objA.print.call(objB); // I am from objB objA.print.apply(objB); // I am from objB

Preserving Method Context with setTimeout

This example shows how to preserve the this context when calling a method asynchronously using setTimeout.

The delayMethod function returns a wrapper that defers method execution by 1 second while ensuring that this still refers to the original object.

  • user.greet() uses this.name, so it’s crucial to call it with the correct context.
  • Wrapping it with obj[methodName](...args) inside the timeout maintains the correct this.
Preserving Method Context
function delayMethod(obj, methodName) { return function (...args) { setTimeout(() => obj[methodName](...args), 1000); }; } const user = { name: "Alice", greet(greeting) { console.log(`${greeting}, ${this.name}!`); }, }; const delayedGreet = delayMethod(user, "greet"); delayedGreet("Hello"); // After 1 second: "Hello, Alice!"

Custom bind() Implementation

This example demonstrates how to implement a simplified version of Function.prototype.bind().

The customBind function:

  • Stores a reference to the original function.
  • Returns a new function that, when called, invokes the original with a fixed this context and any partially applied arguments.

This mimics native bind() behavior, including partial application of arguments.

customBind
Function.prototype.customBind = function (context, ...args) { const originalFunction = this; return function (...newArgs) { return originalFunction.apply(context, [...args, ...newArgs]); }; }; function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: "John" }; const boundGreet = greet.customBind(person, "Hello"); boundGreet("!"); // "Hello, John!"

Sharing Methods Across Objects with call()

In this example, the calculateTotal method from store1 is reused for store2 by using Function.prototype.call().

  • call() sets this to store2, so the method computes tax using store2.taxRate.
  • As an alternative, the method is directly assigned to store2, allowing normal invocation syntax.
Sharing Methods Across Objects
const store1 = { taxRate: 0.1, calculateTotal(price, quantity) { const subtotal = price * quantity; return subtotal + subtotal * this.taxRate; }, }; const store2 = { taxRate: 0.2, }; const price = 50; const quantity = 2; // Calculate total for `store2` using the method from `store1` const total = store1.calculateTotal.call(store2, price, quantity); console.log(total); // 120 (price * quantity + tax) // 2 solution store2.calculateTotal = store1.calculateTotal; console.log(store2.calculateTotal(price, quantity));

bind() for Context in Rate-Limited Function

This example demonstrates how to use bind() to ensure the correct this context when passing an object’s method to a wrapper function.

  • logger.log is passed into rateLimit() which returns a throttled version of the method.
  • Since log relies on this.message, bind(logger) is required to preserve the context.
  • Without bind(), this.message would be undefined when log() is called inside the throttled wrapper.
rateLimit
function rateLimit(fn, ms) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall < ms) return; lastCall = now; return fn(...args); }; } const logger = { message: "Rate limited log", log() { console.log(this.message); }, }; const rateLimitedLog = rateLimit(logger.log.bind(logger), 1000); rateLimitedLog(); // Rate limited log rateLimitedLog(); setTimeout(rateLimitedLog, 1500); // Logs "Rate limited log" after 1.5s

Using bind() to Preserve this

In this example, greet() is a regular function that relies on this.name. When it’s not called in the context of an object, this would be undefined.

To ensure the correct context (person), we create a bound version of the function using bind(person).

This guarantees that this.name inside greet() always refers to "John", even if called independently.

bind
const person = { name: "John", }; function greet() { console.log(`Hello, ${this.name}`); } // Create a bound function const boundGreet = greet.bind(person); boundGreet(); // Hello, John

Arrow Functions Ignore call

In this example, the arrow function is defined inside the greet() method using an arrow function.

Arrow functions do not have their own this — they inherit this from their enclosing lexical context. So even when using call() with a different this value ({ name: "Bob" }), the arrow function still uses this.name from the outer greet() context, which is user.

Thus, the output is: Hello, Alice.

call
const user = { name: "Alice", greet() { const arrow = () => console.log(`Hello, ${this.name}`); arrow.call({ name: "Bob" }); // Arrow functions ignore `call` }, }; user.greet(); // Hello, Alice

Preserving this in Callbacks

When passing a method like user.logName to another function (e.g. executeCallback), the this context is lost unless it is explicitly preserved.

  • bind(user) ensures that this always refers to user.
  • An arrow function () => user.logName() also preserves the context by calling the method directly on user.

Both approaches ensure the correct this context, so the output is: Alice.

this
const user = { name: "Alice", logName() { console.log(this.name); }, }; function executeCallback(callback) { callback(); } // Preserve context with `bind` executeCallback(user.logName.bind(user)); // Alice executeCallback(() => user.logName()); // Alice
Last updated on