In this article I'm going to show you some of the basic usages of call/apply & bind and the problems they solve. I will also show you some practical examples in order to start using them in your projects.
Call/apply & bind are all methods within the function prototype. Both methods are doing the same thing but slightly differently. What they actually do is allows us to call a function with a given this context and arguments. They let us call a function and have access to the properties of another function or object. We can borrow methods of one object’s prototype and use it to another, for example we could apply Array’s slice method to a string or use Math’s max to find the maximum number of a bunch of numbers in an Array.
WHAT IS "THIS"?
I'm not gonna dive into much details here but understanding the this keyword is crucial to grasp the concept of call/apply & bind.
Everything in javascript is an object, functions are objects too (first-class objects). Every object(or function) has a this object assigned to it. this object acts as a reference to the object's variables and methods:
let obj = {
name: "Alex",
getName: function() {
return `My Name is: ${this.name}`;
}
}
obj.getName(); // "My Name is: Alex"
this.name is a reference to the object's(obj) name property value. In other words this refers to the properties of this particular object.
But what if we try to access the name variable outside of the object?
let name = "Alex";
let obj = {
getName: function() {
return `My Name is: ${this.name}`;
}
}
obj.getName(); // My Name is: Undefined
Now we get undefined because name is no longer within our local variable environment.
CALL METHOD
Call accepts a this value and a list of arguments:
Function.call(this, arg1,arg2,...)
Let's say we have an object with a food property key and a favFood function that accepts a string as an argument. In order for favFood to have access to the object's food key we need to call favFood using the call method and give it the _this context of the obj. In simple words we need to attach the obj to favFood:
let obj = { food: "Pizza" }
function favFood(text) {
return `${this.food} ${text}`;
}
let text = "is awesome!";
favFood.call(obj, text); // "Pizza is awesome!"
As you can see we've just passed the object (obj) as a first parameter to the call method -therefore it's context (this)- and a single argument as a second parameter to our favFood function. This way we have access to any method and property of this particular object (obj).
We could also pass multiple arguments seperated by a comma.
APPLY METHOD
Apply is the same as call but instead, apply accepts a single array of arguments.
Function.apply(this, array)
let obj = {
add: function(a,b,c) {
return a + b + c;
}
}
function sumItUp(a,b,c) {
return this.add(a,b,c);
}
let numbers = [1,2,3];
sumItUp.apply(obj, numbers); // 6
BIND METHOD
Τhe tricky part about bind is that it has the same functionality as apply but instead of calling the function immediately, it returns a bound function:
let obj = {
add: function(a,b,c) {
return a + b + c;
}
}
function sumItUp(numbers) {
return this.add(...numbers);
}
let numbers = [1,2,3];
let bound = sumItUp.bind(obj); // Returns a bound function
bound(numbers) // 6
In this case we pass the obj to the sumItUp function -in order to have access to the obj context - then we call the bound function and pass an array of numbers as an argument. Nice thing about bind is that you can call the returned bound function whenever you need.
BORROWING PROTOTYPE METHODS
The cool thing about these methods (call, apply, bind) is that we can borrow methods and functionality from other object's prototype.
Borrowing Max from the Math Object
Let's say we have an array of numbers and we need to find the maximum number within the array:
let numArray = [1,3,4,5,6];
Now we know that the Math object has a method for finding the minumum and maximum values of a list of numbers, but arrays are not supported because they're not considered as numbers therefore are not a valid parameter. If we try:
let numArray = [1,3,4,5,6];
Math.max(numArray); // NaN
We'll get NaN (Not a Number) and that's totally normal beause an array is not a number:
Array === Number // False
But here's where the cool part comes, by using apply we can pass our array as an argument to the Math object like this:
let numArray = [1,2,3,4,5];
Math.max.apply(null, numArray); // 5
Here we pass null as the first argument because we don't need to pass any context to the max method, instead we only use the second argument to pass our array which is going to be converted into arguments and finally be accepted by max as a valid parameter.
Borrowing Filter from the Array Object
Now let's say we have some letters and we'd like to filter out some of them and store them in an array using the Array's filter method.
let letters = 'abcdef';
let arrayFilteredLetters = letters.filter(letter => letter);
console.log(arrayFilteredLettes); // Filter is not a function.
We get filter is not a function because letters are a String object thus it doesn't have access to Array's filter method. What we could do is use call again to invoke filter and pass letters as an argument.
let letters = 'abcdef';
let arrayFilteredLetters = Array.prototype.filter.call(letters, letter => letter !== 'c' && letter !== 'd');
console.log(arrayFilteredLetters); // [ 'a', 'b', 'e', 'f' ]
As you see, we can use call/apply & bind to borrow methods from one object’s prototype and use it to another. This is one of the coolest applications of call/apply & bind.
Important notes about Arrow Functions
In case of arrow functions our methods: Call/Apply & Bind doesn’t work as expected.
As the documentation of MDN states:
"Since arrow functions do not have their own this, the methods call() or apply() can only pass in parameters. thisArg is ignored."
Arrow functions doesn't have their own this. This is lexically bound and it uses the this of the context in which the arrow function was called. Call/Apply & Bind can be used only to pass parameters.
CONCLUSION
By now you should be able to understand the basic usage and applications of call/apply & bind and be able to attach different contexts to functions and objects. You’ll also be able to borrow methods from other prototypes and use it to pass unrelated values -like looping over a string using array’s filter-
In javascript there are countless ways to do something. All the examples above has many alternative syntaxes and methods to have the same results. In this article I decided to use simple examples in order to make sure you get the basic knowledge in the most simplest way.
Have Fun!
Top comments (5)
Nice work Alex.
A small correction upon the 'this' keyword.
A 'this' variable is created when a new execution context is created. And its value it might be anything.
Thank you Sergio!
Yes of course..! I don't mention how or when "this" is created. Just stating the fact that every object has a "this" assigned to it!
The value of "this" might be anything within the context that is created. Of course you can change that with the methods mentioned in this article ;)
Sorry, I did not make it clear.
Every object doesn't have a 'this' assigned to it.
The 'this' is related to the execution context. And the execution context means how a function is called.
That's why I mentioned that a 'this' variable is created when a new execution context is created. :)
As I've already mentioned in the article....I don't go into much details about "this" and
the execution context. The purpose of this article is for a beginner to have a basic understanding of how call, apply and bind works. Saying that every object or function has it's own "this" keyword/variable/object (say it however you want) is enough to make someone understand the basics. Maybe I'll write an advanced article on these concepts next time.
thanks again for the feedback Sergio.
Cool 🔥