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 fromthat
andself
.
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.