JavaScript Elders — Call, Apply, Bind
Before the beginning of time, there were these JavaScript elder functions call, apply and bind. These functions, it seems, can only be wielded by coding beings of extraordinary wisdom. These wielders can build the coding architectures, patterns, principles, monuments using these elders. Pretty dramatic, isn’t it? It sounds exactly the same when someone tries to explain the call, apply and bind functions. In this article, we will try to understand and simplify a not but little confusing topic the call, apply and bind functions.
Basically call, apply and bind functions are used to manipulate how this keyword will going to behave within a function. The concept of this keyword in JavaScript is slightly different as compared to other programming languages, this keyword mostly focuses on the context in which it is used as compare to the instances. The way to use these functions are not that complicated, rather why to use them in the first place is difficult to digest if you are a beginner or coming from some traditional OOPs background (C#, Java, etc.).
We have already seen a cameo of call, apply and bind function in our previous article This is awkward in JavaScript; where it has been used as an Explicit Binding technique for this keyword. In this article, we will first understand what and how to use these functions, and then we will move on with the why part; where we will talk from the usage perspective of these functions and some practical applications.
What and How to — Call, Apply and Bind
The call, apply and bind functions, provide a way to implement explicit binding for this keyword. Using which you can control the object which will be referred to when you use this keyword inside the function. Let us directly jump into coding and understand how to achieve it.
Let us create a function that will be responsible for calculating the total amount of a shopping cart by applying tax and discount to the amount.
function calculateTotal(tax, discount) {
const taxAmount = this.amount * (tax / 100);
return +(this.amount + taxAmount - discount).toFixed(2);
}
Here the function takes tax and discount and calculate the total based on the amount associated with this keyword. Whatever the value for this.amount is dependent on how this function is being invoked.
Let us create an object with having similar property named amount.
const bill = { amount: 112 };
You can have the elders with you anywhere at any point in time. You simply have to write your function name followed by a dot and your respective elder function name. Enough talking, now let us decide the future of this keyword using these elder functions.
The Call Function
The call function takes the object which will be the current instance within the called function and can be accessed using this keyword. Apart from the object, all the subsequent parameters to the call function will be the parameters required by the calling function. Let us verify this with the above calculateTotal function.
const total = calculateTotal.call(bill, 18, 15);
// Total: 117.16
Here we are making a call to the calculateTotal function with the help of the call function where we are passing the bill object as the first parameter which will be the instance within the function at the time of execution, followed by the tax and discount parameters; 18% tax and 15 discount amount.
The Apply Function
The apply function is same as the call function with only one tiny impactful difference; that is except for the main object the function parameters are passed as an array and not as an argument. Let us verify this with the above calculateTotal function.
const total = calculateTotal.apply(bill, [18, 15]);
// Total: 117.16
Here we are passing the bill object as the first parameter and the calculateTotal function parameters are sent as an array in place of the second parameter of apply function; 18% tax and 15 discount amount.
This small difference is very handy in a situation where you want to call a function where it takes any number of parameters but as an argument and you are having data in form of the array which again not of fixed size. For the case consider the Math.max() function; for this Math.max(58, 85, 10) it will simply return 85. Suppose instead of individual comma-separated values we have an array of numbers [58, 85, 10].
Math.max.apply(0, [58, 85, 10]);
As per today’s date it is necessary to mention the cool amazing spread operator which even further simplify the above code; thanks to ES6(ES2015).
Math.max(...[58, 85, 10]);
The Bind Function
The bind function creates a new function of the original function for the supplied object. The catch is that the newly created function will only work with the supplied object. This will help you to have the desired reference bound to a function with added freedom to use the newly created function whenever you want to, unlike the call and apply function which immediately invokes the function. Let us verify this with the above calculateTotal function.
const calculateTotalReloaded = calculateTotal.bind(bill);
const total = calculateTotalReloaded(18, 15);
// Total: 117.16
Here the bind function will return a new function named calculateTotalReloaded which will work with the supplied bill object. This means that any reference to this within calculateTotal will points to the bill object. Now the calculateTotalReloaded function does the same thing but only for the bill object and will return the total.
If we continue binding a new function for the calculateTotalReloaded function for a different object, it won’t have any effect and will return the same total as that of calculateTotalReloaded function if supplied tax and discount remains the same; because the reference will remain tied up to the bill object. Let us check that out:
const calculateTotalReloaded = calculateTotal.bind(bill);
const newBill = { amount: 212 };
const calculateTotalReloadedReloaded = calculateTotalReloaded.bind(newBill);
const total = calculateTotalReloadedReloaded(18, 15);
// Total: 117.16
Why — Practical Application of Call, Apply & Bind
Now we know that how the drums are rolling, but why? What is the actual need to call a function in such a weird (call, apply and bind) possible way? This was the first question that actually strike me and been one of the reasons for my sleepless night. Theoretically, with the help of these functions, you can basically control instances used within functions. On a smaller scale, this definition doesn’t actually satisfy you; because it is a very small part of a big puzzle. To understand better, we will create one scenario where we can feel the need to take help from these functions.
Consider the below Circle class, which has simple functions related to some circle calculations.
const Circle = require('./circle')
const circle = new Circle(3);
console.log('Area:', circle.area());
console.log('Circumference:', circle.circumference());
console.log('Diameter:', circle.diameter());
console.log('Area of Sector:', circle.areaOfSector(60));
console.log('Length of Arc:', circle.lengthOfArc(60));
// Area: 28.274333882308138
// Circumference: 18.84955592153876
// Diameter: 6
// Area of Sector: 4.71238898038469
// Length of Arc: 3.141592653589793
A Pizza Situation
Now suppose there is a situation where your pizza lover but nerd friend wants to calculate the area of the pizza slice he had for himself. For this, he doesn’t actually want to write formulas for calculating the area for the slice. Instead, he just wants to focus on his pizza code logic. Now he can make use of the above well-written Circle class’s areaOfSector function which will solve the purpose. But he doesn’t want to put a direct dependency of the Circle class inside his awesome pizza code. He just wants to borrow the functionalities of the Circle class without depending much on the class. Elder functions to the rescue!
Your pizza nerd friend can take help of any of the elder functions and can utilize the functionalities of the Circle’s functions without even instantiating the objects. Check out the below code:
Using Call Function:
const Circle = require("./circle");
const pizza = { radius: 4 };
const fullPizza = Circle.prototype.area.call(pizza);
const slice = Circle.prototype.areaOfSector.call(pizza, 45);
console.log(`Full Pizza: ${fullPizza.toFixed(2)}`)
console.log(`Slice: ${slice.toFixed(2)}`)
// Full Pizza: 50.27
// Slice: 6.28
Here we are making use of the call function to borrow the Circle class area and areaOfSector functions. The only requirement is that the supplied object must possess the properties which are required to perform the operations. So for that, we have a pizza object which of course has a radius to it to denote the size of the pizza. We can obtain the same behavior using the other elder functions too. Check out the below code:
Using Apply Function:
const fullPizza = Circle.prototype.area.apply(pizza);
const slice = Circle.prototype.areaOfSector.apply(pizza, [45]);
Using Bind Function:
const area = Circle.prototype.area.bind(pizza);
const areaOfSlice = Circle.prototype.areaOfSector.bind(pizza);
const fullPizza = area();
const slice = areaOfSlice(45);
The above situation is of course not so practical one but can be considerable. So the point is, in situations where you want to achieve something with the help of different components/classes/functions, call, apply and bind function provides a way to borrow functionalities from other components without implementing them on a full scale. In this way, we can develop architecture/design patterns using these elder functions.
Git Repository
Check out the git repository for this project or download the code.
Summary
The call, apply and bind methods are not something which will be very frequently used in most of the code logic but can be very handy in situations where you want to extend functionalities beyond your scope. Another reason to learn and know them is the way they control this keyword behavior; because even if you won’t see many scenarios for the call, apply and bind functions but this keyword will definitely be around in your JavaScript’s coding journey. So it is always better to know about call, apply and bind before they know you.
Hope this article helps.
Originally published at https://codeomelet.com.
Top comments (0)