In JavaScript, all functions are objects, which means they can have their own methods. In other words, your functions can have functions. JavaScript has 3 built-in function methods of particular interest: call()
, apply()
, and bind()
. This article is the first of a three-parter covering them, starting with call()
.
Invoking Functions with call()
The call()
function method, well, calls a function. The first argument it accepts is the object to use for this
(more on this later). The rest are arguments to be passed along to the function being called (if any). The following example invokes sayThings()
twice, with the same result both times.
function sayThings(something, somethingElse) {
console.log(something + ' and ' + somethingElse);
}
// logs "meow and woof" x 2
sayThings('meow', 'woof');
sayThings.call(this, 'meow', 'woof');
Controlling Scope
Alright, that seems pretty useless, but stay with me. The good stuff comes when we start playing with this
. By changing what this
is, we can control the scope in which a function is invoked.
function sayFavoriteFood() {
console.log(this.favoriteFood);
}
// create a desmond object
var desmond = {
favoriteFood: 'tuna'
};
// create a maya object
var maya = {
favoriteFood: 'ribeye'
};
// invoking from the global scope where favoriteFood doesn't exist
sayFavoriteFood(); // logs undefined
// invoking as if within the desmond object
sayFavoriteFood.call(desmond); // logs "tuna"
// invoking as if within the maya object
sayFavoriteFood.call(maya); // logs "ribeye"
Wrangling Function Arguments
Every function auto-magically has an array-like local variable named arguments
that provides the (you guessed it) incoming arguments.
function logArguments(a, b, c) {
console.log(arguments);
}
logArguments(1, 4, 9); // logs [1, 4, 9]
Notice I said arguments
was array-like. Although it stores a sequence of values just like an array does, it doesn’t have any of the nice methods for working with those values.
// bad code ahead
function logJoinedArguments(a, b, c) {
var joined = arguments.join('-'); // error, arguments doesn't have join()
console.log(joined);
}
logJoinedArguments(1, 4, 9);
If only arguments
could borrow those handy array methods… Good news! It can, thanks to call()
!
function logJoinedArguments(a, b, c) {
var joined = Array.prototype.join.call(arguments, '-');
console.log(joined);
}
logJoinedArguments(1, 4, 9); // logs "1-4-9"
Be careful, though. join()
is safe, but many other array methods make in-place alterations that can change your incoming arguments. Take reverse()
, for example.
function reverseArguments(a, b, c) {
console.log('a = ' + a);
Array.prototype.reverse.call(arguments);
console.log('a = ' + a);
}
reverseArguments(1, 4, 9) // logs "a = 1" then "a = 9"
Done intentionally, this might have clever applications, but the potential for unexpected side effects is huge. It’s probably safer to copy the arguments to a new array and do your meddling there. Fortunately, Array.prototype.slice()
makes copying easy, and arguments
can borrow it with call()
.
var argsCopy = Array.prototype.slice.call(arguments);
If you prefer shorter code, you can use []
, although it is slightly less efficient to run.
var argsCopy = [].slice.call(arguments);
Intermission
To summarize, call()
is useful because it lets you change what this
is. Such a thing might seem weird at first, but hopefully I’ve shown how useful it can be.
That’s all for now. Next up: apply()
.