Why the .Call() Method?
JPStupfel
Posted on May 11, 2022
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} ]
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.`}
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.
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.`}
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);
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.
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: ƒ}
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.
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} ]
*/
A quick refresher on the .call() method
As a reminder, the syntax for the .call() method looks like this:
function.call(thisArg, arg1, ... , argN)
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} ]
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.'
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} ]
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} ]
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.'
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.
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.`}
.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.'
There we get our desired output, and...
navies[0].report()
//'Aqua Orcs: 100 sailors.'
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!
Posted on May 11, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024
November 30, 2024