DEV Community

JPStupfel
JPStupfel

Posted on

Why the .Call() Method?

Hello Coders,

The DOT CALL Method and Why We Would Ever Want to Use It

When I was introduced to the .call() method, my first though was, why would anyone want to use this? Why not just rely on passing arguments directly to the function instead of messing with context.

In the below blog post I hope to answer that question.

The first few sections jump around a bit, in an attempt to frame the problem. Finally, I think we do a pretty good job discovering a situation where the .call() method is not only useful, but necessary. So, buckle up and let's dig in. Please feel free to code along :-)

Arguments for Space Orcs

Passing Arguments to a Function

To get things rolling, let's say that you are creating a real time strategy space war game. To keep track of each player's forces, you have them organized using nested objects:

const armies = [ {name: 'Space Orcs', fighters: 10000} , 
{name: 'Moon Marines', fighters: 2000} , 
{name: 'Interstellar Scouts', fighters: 2000} ]

Enter fullscreen mode Exit fullscreen mode

Let's make a reporting function that takes the name of the army and the number of fighters as arguments and returns a string.

function report(name, number){
return `The ${name} have ${number} fighters.`}
Enter fullscreen mode Exit fullscreen mode

Let's make sure this is working by mapping the armies array and console logging the report function of each army.

armies.map(e=>console.log(report(e.name,e.fighters)))

//The Space Orcs have 10000 fighters.
// The Moon Marines have 2000 fighters.
//The Interstellar Scouts have 2000 fighters.
Enter fullscreen mode Exit fullscreen mode

Ok, so that works. However, as mentioned this function relies on arguments being passed to the function.

Same Function, but using "This"

As a primer for things to come, let's look at how we might rewrite this same function using javascript's "this" keyword, rather than passing arguments directly to the function. We would then rewrite the function as:

function report(){
return `The ${this.name} have ${this.fighters} fighters.`}
Enter fullscreen mode Exit fullscreen mode

Ok, great, we have modified our function to only rely on the "this" keyword. But now, how do we go about calling this function?

Let's outline 2 ways to call this function.

How to Call a Function that uses "This"

The Hard Way

The first way we are going to look at is undoubtably the long way and not at all the ideal way as it results in the original object being changed.

Let's break this into two steps. Step one is to add the report function to each nested object:

armies.map( e=> e.report = report);
Enter fullscreen mode Exit fullscreen mode

Step two is to go ahead and console log the result of the report function for each item in the armies array:

armies.map(e=>console.log(e.report()));
//The Space Orcs have 10000 fighters.
//The Moon Marines have 2000 fighters.
//The Interstellar Scouts have 2000 fighters.
Enter fullscreen mode Exit fullscreen mode

Never Use the Hard Way

Ok, so that gave us the console log we wanted, but at a cost. The biggest issue here is that we have inadvertently changed the value of the armies array such that each nested object contains the report function as one of it's items:

[{name: 'Space Orcs', fighters: 10000, report: ƒ},
 {name: 'Moon Marines', fighters: 2000, report: ƒ},
{name: 'Interstellar Scouts', fighters: 2000, report: ƒ}
Enter fullscreen mode Exit fullscreen mode

Thankfully, we can avoid this pitfall and save ourselves a little bit of typing by using the javascript call() method.

The Easy Way

Let's now rewrite the last function call using the .call() method:

const armies = [ {name: 'Space Orcs', fighters: 10000} , 
{name: 'Moon Marines', fighters: 2000} , 
{name: 'Interstellar Scouts', fighters: 2000} ]

function report(){
return `The ${this.name} have ${this.fighters} fighters.`}

armies.map(e=>console.log(report.call(e)))

//The Space Orcs have 10000 fighters.
//The Moon Marines have 2000 fighters.
//The Interstellar Scouts have 2000 fighters.
Enter fullscreen mode Exit fullscreen mode

And the good news? With this method we preserved the original values for the armies array.

console.log(armies)

/*
[ {name: 'Space Orcs', fighters: 10000} , 
{name: 'Moon Marines', fighters: 2000} , 
{name: 'Interstellar Scouts', fighters: 2000} ]
*/
Enter fullscreen mode Exit fullscreen mode

A quick refresher on the .call() method

As a reminder, the syntax for the .call() method looks like this:

function.call(thisArg, arg1, ... , argN)
Enter fullscreen mode Exit fullscreen mode

Not to get too deep into the syntax of the .call() method. Suffice to remember that, when you execute a function, anytime you use the "this" keyword, it refers to the "context object" under which that function was called. Furthermore, if no context has been specified, then the global context is used as a default. Likewise, if the function exists within an object, and you call that function from the object, then the object is used as the context object for any "this" calls within the function.

Ok, that's a little wordy. Let's illustrate with an example:

Calling a function from within an Object:

Say, instead of having defined the "report()" function globally as we did above, we had actually defined it as a function of the "space orcs" object:

const armies = [ {name: 'Space Orcs', fighters: 10000,
report: function(){return `The ${this.name} have ${this.fighters} fighters.`}} , 
{name: 'Moon Marines', fighters: 2000} , 
{name: 'Interstellar Scouts', fighters: 2000} ]
Enter fullscreen mode Exit fullscreen mode

Now that we have defined the report function as an item in the 'Space Orcs' object, anytime we call that function, the 'Space Orcs' object will be used as the context for the "this" keyword:

armies[0].report()
//'The Space Orcs have 10000 fighters.'
Enter fullscreen mode Exit fullscreen mode

Finally, The Challenge

Let's go ahead and remember the question we started off with. That question was, "Why would anyone want to use the .call() method?" For example, why not just pass arguments into the function the way we did at the very beginning of this post?

I think we have arrived at a point in this post where we can begin to answer that question. The answer comes from the example we just illustrated: what happens when we have defined a function within an object? Remembering our last example:

const armies = [ {name: 'Space Orcs', fighters: 10000,
report: function(){return `The ${this.name} have ${this.fighters} fighters.`}} , 
{name: 'Moon Marines', fighters: 2000} , 
{name: 'Interstellar Scouts', fighters: 2000} ]
Enter fullscreen mode Exit fullscreen mode

It is natural in the above case to not pass arguments to the report() function, since you are really only anticipating using report() in the "Space Orcs" context.

Swapping Context

What if we wanted to call that same report() function on an entirely new object. Say, in addition to armies, our game contains the array "navies":

const navies = [ {name: 'Aqua Orcs', fighters: 100,
report: function(){return `${this.name}: ${this.fighters} sailors.`}} , 
{name: 'Mars Pirates', fighters: 200} , 
{name: 'Astro Mariners', fighters: 400} ]
Enter fullscreen mode Exit fullscreen mode

Notice that the 'Aqua Orcs' navy has its own "report()" function. If we call that function we get:

navies[0].report()
//'Aqua Orcs: 100 sailors.'
Enter fullscreen mode Exit fullscreen mode

Ok, so what if we really liked the 'Space Orcs' report() verbiage though, and wanted to call the 'Space Orcs' report() function directly on the "Aqua Orcs" Navy?

The "Hard Way" we illustrated earlier wouldn't really work because it would result in the loss of the "Aqua Orcs" Navy report() function definition:

navies[0].report = armies[0].report;
console.log(navies[0].report())
//The Aqua Orcs have 100 fighters.
Enter fullscreen mode Exit fullscreen mode

That gave us the console log we were looking for, but it also destroyed our "Aqua Orcs" Navy report():

console.log(navies[0].report)
//ƒ (){return `The ${this.name} have ${this.fighters} fighters.`}
Enter fullscreen mode Exit fullscreen mode

.call() Method to the Rescue

On the bright side, we can instead use the .call() method and preserve the "Aqua Orcs" Navy report() while calling the "Space Orcs" Armies report() on the "Aqua Orcs" Navy Object.

armies[0].report.call(navies[0])
//'The Aqua Orcs have 100 fighters.'
Enter fullscreen mode Exit fullscreen mode

There we get our desired output, and...

navies[0].report()
//'Aqua Orcs: 100 sailors.'
Enter fullscreen mode Exit fullscreen mode

We have preserved the "Aqua Orcs" Navy report() to be used in the future.

Wrapping Up

If you stuck with this blog post to the bitter end, I thank you. I know it was a roundabout way to get to the final product but I hope it gave a good example of why, when and how to make use of the .call() method.

That's it for this blog post. See ya!

Top comments (0)