call vs apply vs bind

The call(), apply() and bind() methods in JavaScript allow us to 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 can be used to take a function belonging to another object, and call it for a different object. This is important because of how the this keyword works in JavaScript. (As I said earlier we are going to have a separate article on this topic but simply put the this keyword "ties" to an object where a given function was invoked from)

Let's take a look at 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;
};

Now the question is, how can we create a new Student instance that has a name and an age. (The basic idea here is that a Student is also a Person). Remember, in JavaScript the this keyword will always reference the object where the function was invoked from. Therefore we need a way to "call" one function within the other and assigned it to a different object, along with it's 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 let's us invoke a given function and also allows us to pass arguments to it in a form of an array.

The use-case for apply() would be for situations where we'd be calling a function multiple times but we can also assign a different this object to it. This makes our life easier, take a look at the example below. Also note that apply and call are very similar, with the exception that 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) is a great use-case. Imagine that we have an array with a few elements. We also happen to have another array with some more elements. Of course, we can use the push method to push elements to the array, but when that element is an array, we will end up with an array inside an array:

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

Once we realise this, we would opt-in to use concat but that method does not append the original array but rather creates a new one:

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 ]

This could in fact be the desired outcome, however, what happens if we'd like to append the elements to the original array? apply() to the rescue!

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

bind()

Have you ever heard of "exotic function objects"? It's really a thing in JavaScript (introduced as part of ES2015). Simply put, with the bind() method we can create a new bound function (aka an exotic function object) that will wrap the original function object. Calling this newly created function will in turn execute the function it wrapped.

Why is this useful? Because this way we can "remember" the value of this. Again, as a reminder, the value of this will decided at runtime (aka runtime binding) and this could cause some weird 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 code, even though it looks perfectly fine, will not work. The reason? The function passed as a callback to the map() method will be attached to the global scope, therefore the value of this will be the global oboject, in our case, window. We can verify this by adding a console.log(this) just before the if statement.

The solution is to bind the value of 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.