Call and Apply for Beginners

September 30th, 2015

As you advance as a junior front-end developer you will see Javascript code that looks more and more like this.

 
$.when.apply(null, promiseArray)
 .then(function() {
    var data = arguments;
    ...
 });

The .apply() and .call() methods are powerful tools in Javascript, however they can be hard to understand for a young developer, or even for a seasoned developer! The call and apply methods that people will always ask about in interviews, but when you are writing your own Javascript you can never really think of a good reason to use them, or see a need to use them. Lets break down the difference between them, and look at some practical uses for them. We will also look at some new features from ECMAScript2015 (ES6) that can help us simplify the use of these!

I have already read this article!?

There are a lot of articles out there about this concept. However recently teaching some students, there have been a few cases where .apply() has been used to really simplify things. So I wanted to write an article that has some simple break downs and examples of how to use these methods.

Scope

You can’t really talk about .call() and .apply() without first talking about scope in Javascript. Hopefully the concept of global and local scope is something that you understand, if it is not lets do a quick run down about how that works.

In Javascript if you define anything that is not inside of a function, or something without the var keyword. This variable is considered to be global, meaning it can be accessed anywhere in your program.

var name = "Ryan";

function sayName() {
    console.log('Hi ' + name + '!');
}
console.log(name); //Ryan
sayName(); //Hi Ryan!

Here we created a variable outside of the function sayName so it is accessible inside the function as well as outside of it. If we had done something like this however:

function sayName() {
    name = "Ryan";
    console.log('Hi ' + name + '!');
}
console.log(name); //Ryan
sayName(); //Hi Ryan!

This would still work. Because we have created a variable without the var keyword, it is by default a global variable. However, when you use the var keyword and create a variable inside of a function, that is considered to be a local variable. And only accessible inside that function.

function sayName() {
    var name = "Ryan";
    console.log('Hi ' + name + '!');
}
console.log(name); //undefined
sayName(); //Hi Ryan!

So now name is scoped locally to just the sayName function.

In Javascript we have have the this keyword. The context that this refers too changes based on a few things. By default if you just open the console in your browser, this will refer to the global object. In the case of the browser this is the window object, in node it refers to a different global.

When we use the this keyword inside of an object, this will refer to the object itself.

var person = {
    name: "Ryan Christiani",
    sayName: function() {
        console.log("Hi " + this.name + "!");
    }
}
person.sayName(); //Hi Ryan!

So it is scoped just to that object. Let’s see how using .call() and .apply() can help us change this.

.call()

Functions in JavaScript have a method named .call(). It is used to call a function or method, but you are able to determine the context in which it is being called. For example. Assume we have a person object that just has a single property on it.

var person = {
    name: "Ryan"
};

Also assume we have a function called sayName() that simply prints out a name property from this.

function sayName() {
    console.log(this.name);
}

Running sayName() like this will not work. It will try to grab a property from the window object first. However if we use .call() we can pass in and specify what context we want this to be in our function.

sayName.call(person); //Ryan

Great! Now we have the power to call functions and alter the context in which they are executed on the fly!

With .call() we can also apply our arguments to a function. For example, lets change the sayName() function to accept a parameter.

function sayName(greetings) {
    console.log(greetings +  ' ' + this.name);
}

Normally we could just call this function like sayName('Hello'). However our function has no property name. So we have to use .call() to tell this function to use person as the this in the function, and with .call() we can tell it what arguments call uses!

sayName.call(person,'Hello'); //Hello Ryan

With .call() we are able to specify the context of this in our function, as well as the arguments we want to supply to the function. If you have more than one argument, we just add them to the .call(context,arg1,arg2,arg3,...).

.apply()

The .apply() method is similar to .call() however it allows us to pass an array of arguments instead of one at a time. This is very useful. Lets look at a simple, but very common, example. Math.max only takes a list of arguments, but maybe you are provided with an array of arguments.

var numbers = [12,34,62,54,12];

You could, assuming you know that the length of the array will always be the same, do something like this.

Math.max(numbers[0],numbers[1],numbers[2],numbers[3],numbers[4]);

However there are two things wrong with this code. One is that we are very dependent on the length of the array always being the same, what if we have one more or one less? Also we are doing a lot of writing here. And that is just no good.

With .apply() we can simplify this a bit more!

Math.max.apply(null,numbers) // 62

Using the same pattern as with .call() we specify a context to call Math.max and supply the arguments. In the case of our arguments it is just our array numbers. Our context for this will just be null, since we do no really care about that from Math.max. Internally the JS engine will do something like this.

Math.max(12,34,62,54,12);

Not that exactly, but something like it. Hopefully this gives you an idea of what it is doing!

Lets write our own function that expects an array of arguments. It will be very simple, just to get the point across.

function printNames() {
    for(var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

Here is a simple function that prints out a list of names. But WAIT! What is arguments?! Just like the this keyword every function has a special key word called arguments. This is an array like(more on this later) list of the arguments used to call this function. For example.

function printArgs() {
    console.log(arguments);
}
printArgs('hey',32,'Neat'); //['hey',32,'Neat']

Notice how in our function definition we never define parameters for our function to accept. The function we wrote earlier, printNames uses the same principle. Lets assume you get an array of names, again you do not know what the length will be. Using .apply() we can call this function with this array!

printNames.apply(null,['Ryan','Kristen','Drew']);

This will print out the names!

agruments

Above I mentioned that arguments is an array like list, meaning is it not actually an array. What does that mean? Well it means we don’t get all the methods you are used to from arrays. Methods like .slice(), or .splice(). If you try to call arguments.slice(1), you will get TypeError: arguments.slice is not a function.

However, we have learned how to fix this already! We can use .call(). These methods from the array live on the Array.prototype so in order for us to use something like slice, we need to use .call().

function printArgs() {
    var slicedArgs = Array.prototype.slice.call(arguments,1);
    console.log(slicedArgs);
}

printsArgs(['Ryan','Kristen','Drew']) // ['Kristen', 'Drew']

We use .call() on Array.prototype.slice, we set arguments as the context of the method and provide our argument to use for .slice()! Using .call() to manipulate arguments is a very common pattern you will see. But let’s now look at a different way of doing this!

A Different way

.apply() and arguments are great, but what if there was a better way to do this? A way that was less verbose? In ECMAScript 2015 (ES6) we get two new features. The Spread Operator and Rest Parameters. Lets look at how we can use these to do something similar to using .apply and arguments.

Spread Operator

The Spread Operator is a new feature that we can use to, well, spread our arguments. The syntax for the Spread Operator is ...arrayName, consider the function below.

var names = ['Ryan','Drew','Kristen','Wes'];
function print() {
    for(var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    } 
}

print(...names);

What the Spread Operator is doing, is something similar to .apply().

print('Ryan','Drew','Kristen','Wes');

You can actually mix and match the use of the Spread Operator.

var names = ['Ryan','Drew','Kristen','Wes'];
function print() {
    var nameList = Array.prototype.slice.call(arguments,1);
    for(var i = 0; i < nameList.length; i++) {
        console.log(arguments[0] +  ' ' + nameList[i]);
    } 
}

print('Hello',...names);

In this new addition to the function we use a pattern we saw earlier to store a version arguments into a new variable called nameList. We also use arguments to access the string Hello from the function call. This is getting a little out there however, there is a lot going on. We are utilizing what we learned earlier, but this can get better!

Before we move on I just want to point out that the Spread Operator is not a replacement for .apply() in all cases, it does not allow us to control the context of a function call, rather it helps us with arrays of values we need to supply to functions.

Rest Parameters

The flip side of the Spread Operator is Rest Parameters. We can use this as a solution to the array like nature of arguments. The syntax looks like this ...args, pretty much exactly like the Spread Operator! The difference here is where you use the syntax. Let’s change our function above to work with Rest Parameters.

var names = ['Ryan','Drew','Kristen','Wes'];
function print(greetings, ...nameList) {
    for(var i = 0; i < nameList.length; i++) {
        console.log(greetings +  ' ' + nameList[i]);
    } 
}

print('Hello',...names);

Here we add the greetings parameter as well as this new ...nameList. We now get access to an actual array in the form on nameList. Because of this we can change the for loop to be an .forEach() loop, since that is an Array method!

var names = ['Ryan','Drew','Kristen','Wes'];
function print(greetings, ...nameList) {
    nameList.forEach(function(name) {
        console.log(greetings +  ' ' + name);
    })
}

print('Hello',...names);

That looks a little nicer!

For more information about these check out Rest Parameters and the Spread Operator on MDN. Note that these features are very new and only available in browsers that support ECMASCript 2015. However check out my post on ES6 easy wins to find out about some new features and also how you can use them today! (hint, it’s Babel).

Hope that helps!

  • Jason Belcher

    Wow Now I understand call() and apply() much better! Thank you!