Implement your own — call(), apply() and bind() method in JavaScript
It completely makes sense to not reinvent the wheel at your work, but it’s also of great significance sometime to build some smaller wheels yourself for the purpose of learning. Simulating these small wheels in a comprehensive way does enhance your own learning of the same.
“What I Cannot create, I do not understand” — Richard Feynman
So let’s try to simulate the method call
, apply
and bind
in JavaScript.
Call Method
First of all, let’s take a look at what original call
method is.
Example of call
:
The above example will give us output welcome Ankur Anand
.
So if we analyze the basic principle of a call function from above, we notice these things.
- Calling the prototype function call changes the pointing of this. i.e Function call in the above became
obj.showProfileMessage
- Whatever arguments we have passed to
showProfileMessage.call
should be passed to originalshowProfileMessage
asarg1, arg2,...
. - Does not cause side effects to
obj
andshowProfileMessage
Function, i.e callingcall
doesn’t modify the originalobj
orshowProfileMessage
by any mean.
Let’s try to implement the first step of the idea of call. As the call
method prototype method of Function
Object. Our custom myOwnCall
will be also be attached to the Function
prototype.
With that, we have achieved the first step of this, If you are wondering how take a look closely at this.
showProfileMessage.myOwnCall(obj, "welcome ");
What happens when we call something like above, showProfileMessage
is an object (Function is also an object in JavaScript) on which we are calling the method myOwnCall
inherited from prototype, so inside our myOwnCall
method currently the this
variable will be pointing to the object that is showProfileMessage
and this is what our function reference is, so we added a function referencing showProfileMessage
to new passed obj
or this
value from the myOwnCall
with
someOtherThis.fnName = this;
and then simply called the function in the next line.
Let’s now implement the second idea of call
, to call the function with the passed parameters of variable length.
Before that let’s look at the eval
function in JavaScript.
The
eval()
function evaluates JavaScript code represented as a string.
eval
takes a string representing a javascript expression, statement, or sequence of statements. The expression can include variables and properties of existing objects.
Let’s get the third idea of the call
method. “To not cause any side effect” and that exactly what we have violated till now in our implementation.
someOtherThis.fnName = this;
We are adding fnName
property to someOtherThis
assuming that someOtherThis
does not have a property named fnName
in advance, we should avoid this. We can use a symbol of es6
but we will avoid it as of now. We will use Math.randmon
to create a unique property id
for the object, and should delete this property after execution.
Also if we look at the MDN definition we see that the call
function should return a value with the result of specified this
and arguments
. Also if the value of someOtherThis
is null
or undefined
it should be replaced with a global object (window
for browser and global
for node.js).
Let’s check our final implementation of the same.
Please note that the above function is still not completely safe from a side effect if, for example, the called function iterates over the keys of ‘this’
it would get a different result with the above implementation than the native implementation. (Thanks to LukaLight for this point).
Apply Method
Let’s look at the apply
method definition as on MDN.
It’s very much same as call
method with only difference being its takes an array
like object as arguments. So without any further ado, let’s look at its complete implementation.
Bind Method
Again we will start by looking at the MDN definition of the same.
The general characteristic of the bind function is as follow from mdn.
- The bind method creates and returns a
new function
, called a bound function. This bound function wraps the original function object.
2. Look at this line, from mdn definition “arguments to be prepended to the arguments provided to the bound function, when invoking the target function”.
I will try my best to break it down step by step for you.
If we run the above code the output we get is
Mr Ankur Anand
So If you look carefully the argument “Mr” was provided at the time of creating a bound function bindFullName
while argument “Ankur” was provided at the time of invoking the bindFullName
which in turn invokes the target function. So the argument “Mr” was prepended to the arguments list when we called the bindFullName
with an argument “Ankur”. This is what the definition at the MDN for args1, args2, ...
means.
Let’s us try to implement the same in our own myOwnBind
method.
We are using Array.prototype.slice.call
because arguments
are not array but an array-like object.
Are we done with bind? No, We have still missed one piece from the mdn definition of the bind for thisArg
.
The value is ignored if the bound function is constructed using the
new
operator.
What it means to say is we need to ignore the passed in this
value while creating the bound function when the bound function is called with a new
operator. Taking the example given above
new bindFullName("Ankur");
will give us output asMr Ankur undefined
.
MDN has a nice polyfill of bind that takes care of the new operator scenario. What it basically does is set a transit constructor fNOP, so that the bound function and bind()
the function call is on the same prototype chain, because calling the bound function with the new operator involves the passing of the prototype chain.
You can check the Polyfill of the bind at mdn.
Hope this article proves helpful to you at providing some insight of call, apply and bind in JavaScript as it’s has been for me 😅. If you find any issue or typo please let me know. 😃
More where this came from
This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.
Follow our publication to see more stories featured by the Journal team.