Two functions in Javascript that don't seem to get a whole lot of attention are the .call(..) and the .apply(..) functions. Both of these functions have a similar purpose; the major difference lies in how you plan to pass parameters. If you remember from the last blog entry, we examined briefly the .apply(..) function when we were creating a cross-browser solution for setTimeout(..).
In this entry, I want to focus more on the core purpose of these functions. This will eventually lead me into a future blog entry in which I talk about a solution for IE's lack of 'this' keyword support when using attachEvent(..).
But first we must understand more thoroughly what .call(..) and .apply(..) allow us to do. Read on to learn more.
Given a reference to a function, myFn, it is possible to invoke that function by calling:
At first glace, it doesn't seem like there is a whole lot to gain by doing this. After all, a simple call to myFn(); will also invoke the function.
The real magic happens when you realize that you can manually specify which object you want the function to treat as the current object. Take the following example:
{
this.name = name;
this.color = color;
this.age = age;
}
function frog(name, age)
{
this.name = name;
this.color = 'green';
this.age = age;
}
function showAnimal()
{
var str =
this.constructor.name.toUpperCase() + '\n' +
'name: ' + this.name + '\n' +
'color: ' + this.color + '\n' +
'age: ' + this.age;
alert(str);
}
function init()
{
var c = new cat('fluffy', 'white', 3);
var f = new frog('igor', 2);
showAnimal.call(c);
showAnimal.call(f);
}
Here, we've defined two custom objects: cat and frog, as well as a function showAnimal(..) (notice that the showAnimal(..) function takes no parameters). Instead, showAnimal(..) uses the this keyword (which referes to the current object) to access various properties of the current object.
Now if you're anything like me, then the first time you took a look at this bit of code, your exact thought was proabably "… what?"
It's not that complicated really. Imagine if we had hooked up the showAnimal(..) function inside of the cat or frog class like this:
{
// name, color, age assignments here
this.show = showAnimal;
}
The code snippet above should be pretty routine for most Javascript programmers. The statement that reads this.show = showAnimal; essentially defines a public function of the cat class which is just a reference to the showAnimal(..) function. Since this function is a member of the cat class, it has access to all of cat's internal member variables.
The issue here is that we'd have to hook up the .show(..) function for both the cat and the frog class. And if we decided to create more animals that had the properties name, color and age, then we'd have to hook up the .show(..) function for those classes as well.
Notice how our first code snippet alleviates the need to hook up the showAnimal(..) function individually for each class. By invoking the showAnimal(..) function via .call(animalRef), we tell the Javascript interpreter to treat animalRef as the current object (the object referred to by the this keyword).
If you wanted to pass additional parameters to the showAnimal(..) function, you could easily do that. Simply specify the paramaters you wish to pass in the same way you would any other function — only, do it after the first parameter, as in the following example:
saySomethingToCat.call(myCat, 'Hello there', 'How are you?');
function saySomethingToCat(msg1, msg2)
{
alert(msg1 + ' ' + this.name + '. ' + msg2);
}
In this example, we passed in 2 (additional) parameters to the function saySomethingToCat(..). It's important to note that even those we're passing in 3 parameters (myCat, 'Hello there', and 'How are you?'), the first parameter, myCat, doesn't really count becuase it is this object that will be treated as the current object (and hence, the one referred to by the this keyword inside the saySomethingToCat(..) function).
The .apply(..) function works in almost exactly the same way, but with one minor difference (and if you read my last blog post, you probably already know what it is).
Instead of passing additional parameters as a comma delimited list as you would with the .call(..) function, you simply pass an array containing the data you want wish to pass as parameters. This can be very useful if the data you want to pass is already in an array (as it was when we were dealing with the arguments array in the last blog entry).
Rather than create a whole new example to demonstrate the .apply(..) function, I'm simply going to refer you to my previous blog entry regarding cross-browser setTimeout(..). I like to think it's one of the more brilliant Javascript functions I've come up with anyway, so I think everyone should read it
As a final note, I like to say that I, personally, have found very few uses for the .call(..) and .apply(..) functions. One example is obviously the setTimeout(..) function in my last blog entry that I won't shut up about, and another is the ability to fix a this keyword reference in IE (but that is another blog entry — hopefully coming soon).
That doesn't mean there aren't more uses. It's just that, for almost every example I've seen using .call(..) and .apply(..), I can imagine other ways that I like better to accomplish the same thing. But if any readers out there have know of other useful way to take advantage of either of these functions, feel free to send me an email. I'd love to hear from you.
That about wraps up this entry. As always, comments welcome.