JavaScript Functions with Variable Arguments

JavaScript

You’ll often come across JavaScript libraries that let you pass different types and numbers of arguments to the same function call. This can be a really useful feature and it often makes your code easier to read.

So how do these libraries make this happen and how can you do this with your own functions? Take for example a function that sums a list of numbers.

var total = sum(1,2,3,4);

While we could supply the arguments as an array (e.g. sum([1,2,3,4])), it’s cleaner without.

A solution

Fortunately there’s a solution. JavaScript functions have a hidden variable called arguments that gets set to the list of arguments each time the function is called. This is an “array-like” variable allowing you to loop over each of the arguments.

Writing our sum function above to make use of this we would end up with:

function sum() {
    var total = 0;
    for(var i=0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

Functions with multiple signatures

You can also use arguments to handle different signatures for the same function and even different return types. You may have seen this used to great effect in JQuery.

Take for example JQuery’s css method. This has five different ways it can be called, two as a getter and three as a setter.

As a getter

// 1. Gets the value of a single style property
var val = $(elem).css('color');

// 2. Returns an array of values with values for each supplied property
var vals = $(elem).css(['background-color', 'font-family', 'font-size']);

As a setter

// 3. Sets the value of a single style property
$(elem).css('color', '#FFF');

// 4. Sets the value of the style property based on the a function
$(elem).css('width', function() { return Math.ceil(Math.random() * 100); });

// 5. Sets multiple property values supplied as an object hash
$(elem).css({
    width: 100,
    height: 200,
    color: '#00FF33'
});

Let’s see how the skeleton of such a function would look.

/*
 * Set the CSS for an element.
 */
function css(arg1, arg2) {
  if(arguments.length == 1) {
    // Could be either option one, two or five at this point
    if(isArray(arg1)) {
      // option 2
      // return values for each property in list arg1
    } else if(isHash(arg1)) {
      // option 5
      // set style for each property/value in hash arg1
    } else {
      // option 1
      // simple lookup and return of style value keyed on arg1
    }
  } else if(arguments.length == 2) {
    // We've narrowed it down to options three and four
    if(isFunction(arg2)) {
      // use arg2 as a function and set the property named by arg1
    } else {
      // option 3 - simple key/value setting
    }
  }
}

Detecting argument types

This code assumes some utility functions to detect the argument type. For example isArray is commonly implemented as:

function isArray(val) {
  return toString.call(val) === "[object Array]";
}

Note that we check the type using toString rather than using the instanceof operator or checking against the constructor because that is not reliable when comparing instances across frames.

Make it clear

One downside to this technique is that it’s not immediately clear from a function’s name what it does.

If you take this approach you’ll want to make sure that your code is commented well and if part of a shared library make sure you document it well with clear examples. JQuery does this very well.

You can’t use ‘arguments’ like an array

One little mistake that’s often made is assuming that arguments is actually a fully fledged Array; it’s not. Though it does support access via array subscript notation and it has a length property, it lacks many useful Array methods such as slice and forEach.

Here’s a handy little trick though. If you want to use those methods on the arguments object, simply get a reference to the method’s function object and use call with arguments set as the context. Take for example a function that returns the last three parameters you pass to it.

function lastThree(params) {
  if(arguments.length < 3) {
    return [].slice.call(arguments);
  }
  return [].slice.call(arguments, -1);  
}

console.debug(lastThree("doe","foo", "bar", 'fi')); // prints ["foo", "bar", "fi"]

How it works

The parameters passed into lastThree are available in the arguments object. slice is a handy method on Array that returns the elements between two array indices as an array. That’s simpler than building an array up with a for loop.

As slice is not present on the arguments object, we’ll invoke it indirectly. [].slice creates a throwaway array object purely to get access to the slice function object. We then use its call method to invoke slice with a given context and arguments.

Let’s break this technique down into two parts.

// First get a reference to slice's function object
// (we could alternatively use Array.prototype.slice here).
var funcObj = [].slice; 

// Then call the function with the given context and arguments                                              
functObj.call(context, arg1, arg2, etc);

In lastThree by passing the arguments object as the first parameter to call, we set that object as the context. In other words slice will see arguments as its value for this.

Because the arguments object has the length property and supports the subscript access needed by slice to do its job, slice is none the wiser and happily works with it. This is a good example of duck typing in JavaScript.

Note that the call method itself allows for variable argument numbers, as does slice. It’s a very common pattern and one that you can use to great effect in your own code.