Skip to main content

call vs apply vs bind

5 min read

Older Article

This article was published 5 years ago. Some information may be outdated or no longer applicable.

The call(), apply() and bind() methods in JavaScript let us change the value of the this keyword.

What the this keyword is, is something we will discuss at a later article especially focussing on how it’s different from that and self.

call

The call() method takes a function belonging to one object and fires it for a different object. This matters because of how the this keyword behaves in JavaScript. (We’ll cover this in a separate article, but the short version: this ties to the object where a given function was invoked from.)

Here’s an example:

// creates a "constructor" for a Person object
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// creates a "constructor" for a Student object
function Student(department) {
  this.department = department;
};

The question: how do we create a new Student instance that has a name and an age? (The idea being that a Student is also a Person.) In JavaScript, this always references the object where the function was invoked from. So we need a way to call one function within the other and attach it to a different object, along with its this value:

// creates a "constructor" for a Person object
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// creates a "constructor" for a Student object
function Student(name, age, department) {
  Person.call(this, name, age);
  this.department = department;
};

const student = new Student('John', 19, 'Software Engineering');
console.log(student.name); // John

apply

The apply() method lets us invoke a function and pass arguments to it as an array.

The use-case: situations where we’d call a function multiple times but want to assign a different this object each time. Note that apply and call are very similar. The difference: call takes an argument list (as we saw above: Person.call(this, name, age)) while apply takes an array.

const users = [
  {
    name: 'John',
    department: 'IT',
  },
  {
    name: 'Susan',
    department: 'Product',
  },
];

// accessing this.name is only possible because of apply used later
function greet(greeting, emoji) {
  console.log(`${greeting}, ${this.name} ${emoji}`);
}
const greetings = ['hi', 'hola', 'ciao', 'yo', 'aloha'];
const emojis = ['😊', '👋', '', '🙋'];
users.forEach((user) => {
  // greet every user with different messages, randomly
  greet.apply(user, [
    greetings[Math.floor(Math.random() * greetings.length)],
    emojis[Math.floor(Math.random() * emojis.length)],
  ]);
});

Another example (borrowed from MDN) shows a great use-case. Imagine you have an array with a few elements, and another array with more elements. Using push to add one array to the other gives you an array inside an array:

const nums = [0, 1];
const moreNums = [2, 3];
nums.push(moreNums);
console.log(nums); // [ 0, 1, [ 2, 3 ] ]

You might reach for concat, but that creates a new array rather than appending to the original:

const nums = [0, 1];
const moreNums = [2, 3];
const newNums = nums.concat(moreNums);
console.log(nums); // [0, 1]
console.log(newNums); // [ 0, 1, 2, 3 ]

That could be the desired outcome. But if you want to append elements to the original array, apply() does the job:

const nums = [0, 1];
const moreNums = [2, 3];
nums.push.apply(nums, moreNums);
console.log(nums); // [0, 1, 2, 3]

bind()

Ever heard of “exotic function objects”? It’s a real thing in JavaScript (introduced as part of ES2015). With bind(), we create a new bound function (an exotic function object) that wraps the original function object. Calling this newly created function executes the function it wrapped.

Why is this useful? It lets us “remember” the value of this. The value of this gets decided at runtime (runtime binding), which can cause unexpected behaviour. See the example below:

const obj = {
  results: [],
  numbers: [1, 2, 3, 4, 5, 6, 7],
  divisible: function (num) {
    this.numbers.map(function (number) {
      if (number % num === 0) {
        this.results.push(number);
      }
    });
    return this.results;
  },
};

console.log(obj.divisible(2)); // TypeError: Cannot read properties of undefined (reading 'push')

The above looks perfectly fine but won’t work. The function passed as a callback to map() gets attached to the global scope, so this points to the global object (in a browser, window). You can verify this by adding console.log(this) just before the if statement.

The fix: bind this to the callback block.

Please note that ES2015 has introduced arrow functions (=>) and using that will also fix the problem at hand.

const obj = {
  results: [],
  numbers: [1, 2, 3, 4, 5, 6, 7],
  divisible: function (num) {
    this.numbers.map(
      function (number) {
        if (number % num === 0) {
          this.results.push(number);
        }
      }.bind(this)
    );
    return this.results;
  },
};

console.log(obj.divisible(2));

You can run all these examples below.

See the Pen call-apply-bind by Cloudinary (@Cloudinary) on CodePen.