Polyfill for Bind method and Currying in JavaScript
What are call, bind and apply methods in JavaScript?
Hello Readers, welcome to another blog on learning the key concepts of JavaScript. We will learn about creating polyfill for the bind method and the currying concept in JS. In order to do that, first, we have to understand how the call, bind and apply method works in JavaScript. We will follow the below path in our journey:
- Learn about the call, bind, and apply method in JS
- Creating Polyfill for bind method
- Concept of Currying in JavaScript.
Call, Bind and Apply method in JS
It's important to know that this keyword behaves differently in JS and is dependent on how a particular function is called. Let's see an example:
const obj1={
favCricketer:"Virat Kohli",
X:function(){
console.log("My Favourite cricketer is "+this.favCricketer);
}
}
obj1.X();
const outerFunc=obj1.X;
outerFunc();
The output of the above code snippet is:
When a function is directly called with reference to its object, this points to that object's scope and it can access the variables defined in the object's scope. But when we try to assign an object's function to another function(without object reference) and it's invoked, this now points to the window object. In our code example, the last line tries to find the value of favCricketer in the global scope and thus prints undefined in the console.
- Methods like call, bind, and apply are used to make this keyword independent of how a particular function is called.
- Call and Apply works almost similarly, the only difference being method of passing arguments. The call method accepts comma-separated values while Apply method accepts all the arguments in a single list/array.
const player1={
name:"Deep",
score:20
}
let showScores=function(hometown)
{
console.log(this.name+ " from " + hometown + " scored :"+this.score);
}
const player2={
name:"Amrit",
score:15
}
showScores.call(player1,"Raipur");
showScores.apply(player2,["Delhi"]);
The call() method calls a function with a given this value and arguments provided individually. The apply() method calls a function with a given this value and arguments provided in a single array.
So in the last 2 lines, this will point to player1 and player2 respectively and output would be :
Deep from Raipur scored 20
Amrit from Delhi scored 15
Now let's understand how bind works and go back to one of our previous examples.
const obj1={
favCricketer:"Virat Kohli"
}
const obj2={
favCricketer:"MS Dhoni"
}
function xyz(){
console.log("My Favourite cricketer is "+this.favCricketer);
}
const Func1=xyz.bind(obj1);
Func1();
const Func2=xyz.bind(obj2);
Func2();
By using the bind method on function, we've bound this to a particular object which could be called at any given point in time. With the call and apply method, the function is called immediately. The output of the above code snippet would be :
My Favourite cricketer is Virat Kohli
My Favourite cricketer is MS Dhoni
Now that we've understood how these special methods work, we can move on to the next section i.e Writing Polyfill for bind method.
Polyfill for Bind method
In the previous section, we have seen how bind can be used to set this to a particular object which can be invoked at a later point in time. What if we have to write our own prototype/polyfill for the bind method. We have to keep the following points in mind:
- Our polyfill method must be accessible to all the functions i.e it should be a function prototype.
- Handling all the arguments passed and references properly.
- It should return a function.
Let's jump to our previous example:
const obj1={
favCricketer:"Virat Kohli"
}
const obj2={
favCricketer:"MS Dhoni"
}
function xyz(runs,status){
console.log("My Favourite cricketer is "+this.favCricketer+" who has scored "+runs+" International runs. He is "+status+
"Captain of Indian Cricket Team");
}
const Func1=xyz.bind(obj1,23000);
Func1("Current");
We can see that the XYZ.bind method returns a bound function that is stored in the Func1 variable. Also, arguments have been passed while applying bind and when Func1 is invoked. So we have to handle all these things in our polyfill. It will look something like this :
const obj1={
favCricketer:"Virat Kohli"
}
const obj2={
favCricketer:"MS Dhoni"
}
function xyz(runs,status){
console.log("My Favourite cricketer is "+this.favCricketer+" who has scored "+runs+" International runs. He is "+status+
"Captain of Indian Cricket Team");
}
Function.prototype.mybind=function(...args)
{
let obj=this;
params=args.slice(1);
return function(...args2)
{
obj.apply(args[0],[...params,...args2]);
}
}
const Func1=xyz.bind(obj1,23000);
Func1("Current");
const Func2=xyz.mybind(obj2,17000);
Func2("Ex");
Let's understand the above code line by line.
We start with writing a function prototype and name our polyfill mybind. Since it returns a function, we are also returning a function in mybind.
When this returned function is invoked, we have to make sure that xyz function is executed i.e that's how bind works.
So we can implement this with the help of this because inside mybind, this will point to XYZ. That's why we have stored the value of this in a variable and calling XYZ(through this variable) inside returned function.
Now let's take care of the arguments. So we have arguments being passed in our mybind method as well as returned function. To handle these scenarios, we pass ...args in mybind function definition and ...args2 in the function returned.
See how args array has stored both object reference as well as next argument. So we have to pass the first element args[0] as the first argument in our apply method and combine elements(excluding first) of args as well as args2 to pass as the second argument.
We use the spread operator to combine both the arrays. And this is the output after final execution:
Hope you've understood how we can write our own polyfill and prototype for a given method. Let's move on to the next section i.e Function Currying in JS.
Currying in JavaScript
Currying is a process in functional programming in which we can transform a function with multiple arguments into a sequence of nesting functions. It returns a new function that expects the next argument inline. It's helpful when we have many places that use a function in exactly the same way. Our implementation will be shorter and more readable.
Currying can be implemented through both bind method and closure concept.
//using bind method
function xyz(x,y)
{
console.log("X: "+ x + " Y: "+y);
}
const func1=xyz.bind(this,2);
func1(3);
const func2=xyz.bind(this,2);
func2(5);
// using closure concept
function abc(x)
{
return function(y)
{
console.log("X: "+ x + " Y: "+y);
}
}
const func3=abc(2);
func3(3);
const func4=abc(2);
func4(5);
The output of the above code will be same in both the methods:
X: 2 Y: 3
X: 2 Y: 5
X: 2 Y: 3
X: 2 Y: 5
When using the bind method, we are explicitly setting the default value of x as 2 and passing variable values of y while invoking the returned function.
When using closures, the inner function in abc forms a closure with the lexical scope of its parent i.e abc, and hence has access to both the variables y and x. Again, we are setting the default value of x as 2 and passing different values of y in different functions.
This is how currying is used in JavaScript to reuse a function in multiple places.
THE END!
Hope you've enjoyed reading this article. We have now understood the implementation of call, apply and bind in JavaScript. Also, we are now familiar with the concepts of writing Polyfill for bind method and function currying in JS.
Reference: youtube.com/watch?v=ke_y6z0xRpk&t=119s
Thank you for reading!