Default Parameters, Episode V

I keep a pretty close eye on the searches that bring people to this blog. A lot of you are searching for how to sort in JavaScript (I’ll have to go into more detail on sorting soon). And a whole mess of you want to know about missing and default parameters.

I’m going to take another shot at the default parameter problem. I gave a solution that adds just one line of code per function, but it requires you to pass all parameters in a single object. I’ll adapt that solution to better handle the normal way of passing JavaScript parameters.

Really quickly, let’s talk about JavaScript parameters.

  1. When you pass in fewer than the expected number of parameters, the missing ones come in as undefined.
  2. When you pass in more than the expected number of parameters, the excess ones can be slurped out of the arguments pseudo-array. This array-like variable has the length method, but is missing other array methods.
  3. A common way to implement default parameters is with a series of statements like this: a=a || 1; The problem with such a statement is that so many different values evaluate to false that it’s easy to make a mistake and prevent a valid number like 0 from coming through.
  4. A better way to implement default parameters is like this: if (a===undefined) a=1;
  5. Another common way is if (a==null) a=1; This works pretty well because null and undefined are loosely equal to each other, but other falsy values aren’t equal to either of them.

Let’s modify my defaultHandler from my previous post on the subject so that it works with arrays instead of objects.

    defaultHandler=function(defaults,params) {
        var i;
        for (i=0;i<defaults.length;i++) {
            if (params[i]!==undefined) {
                defaults[i]=params[i];
            }
        }
        return defaults;
    }

Choose Your Poison

You now have a couple choices on how to use it. After the default handler is called, the array params will hold all your parameters in order.

    function test(a,b,c) {
        var params=defaultHandler([1,"banana",3],arguments);

        console.log(params);
    }

If you really want to, you can stuff the values back into the original named parameters…

    function test(a,b,c) {
        var params=defaultHandler([1,"banana",3],arguments);
        a=params[0];
        b=params[1];
        c=params[2];

        console.log(a,b,c);
    }

It’s a shame there’s no way to automate that stuffing of the parameters back into the original variables, isn’t it?

Ah, but there is. In fact, the whole process can be cleaned up with a bit of metaprogramming and some introspection. But that’s for Episode VI.

Note: This is the second post on default parameters. I took a cue from George Lucas on its numbering to try to get some search hits from the Star Wars zealots.

Advertisements

Missing and Default Parameters in JavaScript

Passing parameters to JavaScript functions seems simple enough. Since the language is weakly-typed, all the parameters in the call are simply comma-separated.

And in the definition of the function it can get no more complicated than that–a comma-separated list of parameters.

Not much room for flexibility, is there?

But let’s take a closer look. Suppose you don’t pass all the parameters that a function is expecting. What happens?

If you shortchange a function, all the parameters you failed to supply are undefined.

What happens if you give more parameters than the function expects? They are ignored, but you can find they are still accessible in an array-like list called arguments. (No, it’s not an array. While it does have a length, it’s missing the methods that come with arrays.)

Now What?

So we’ve revealed a bit of flexibility–JavaScript won’t come to a screaming halt if you supply more or less parameters than a function expects. It’s more flexible in handling parameters than we might have suspected.

Some languages allow you to specify default parameters. Can JavaScript do that? Well, it’s not in the spec, but JavaScript programmers commonly provide default parameters using JavaScript’s || operator.

a = a || 1;

If a is undefined, a will be assigned 1. A will also be assigned 1 if a is 0. That’s not always what you want, so sometimes a condition is best:

if (a===undefined) {
  a=1;
}

Let’s try one. We’ll make a function that takes three parameters.

  • a will have the default value of 1
  • b will have the default value of 2
  • c will have the default value of 3
function test(a,b,c) {
    a=a||1;
    b=b||2;
    c=c||3;
    console.log(a,b,c);
}

We can call it like this:

test(1,2,5);
test(undefined,3,undefined);
test();

And get output like this:

1,2,5
1,3,3
1,2,3

If you have a lot of parameters coming in, that can get messy fast. Is there a better way?

There Is a Better Way

More than a few programmers have taken a stab at providing a nice default parameter implementation, and JavaScript is flexible enough to provide many ways to do it.

Most of these solutions feel over-engineered to me, so I spent some time thinking about the problem and came up with my own solution. I wrote a function called defaultHandler. This is going to be easy! Take a look:

    defaultHandler=function(defaults,params) {
        var i;
        for (i in params) {
            defaults[i]=params[i];
        }
        return defaults;
    }

We start with the “defaults” object that was passed in. Obviously, this holds all the default values for the parameters we expect to get passed in. Then we spin through everything that was passed in as a parameter and add it to the defaults, overwriting values when we run into identical keys.

Simple. Too simple, perhaps. The trick must be in how we use it. Is that it? Well, here’s an example.

    function test(obj) {
        var params=defaultHandler({a:1,b:2,c:3},obj);
        console.log(params);
    }

Hmm. Not much going on there, either. I told you this was going to be easy!

Here, I call our test function:

    test({a:5,x:"tuna"});
    test({});
    test({b:10,y:5});

And get the following output:

Object a=5 b=2 c=3 x=tuna
Object a=1 b=2 c=3
Object a=1 b=10 c=3 y=5

That’s all there is to it. Instead of passing parameters, we always pass in one single object that holds all the parameters.

In effect, we’re ignoring JavaScript’s “almost an array, but not really” parameter-passing system, and using a full-blown object to do the parameter passing work. Since objects can hold anything (numbers, booleans, arrays, objects, functions, probably even regex descriptions), nothing has changed except the packaging and naming of parameters. And we’ve gained tons of flexibility.

Come to think of it, you don’t even really need the default handler. That’s just a nice way of pruning down the parameters which need to be passed.

Why Would I Do This?

Imagine you’ve written an operating system. It has a nice windowing user interface. You have function calls like “OpenWindow” that take some parameters. In a later version of the interface you want to start adding parameters to the function.

It can get messy. You want to keep backwards compatibility so old applications don’t break. So maybe you have a slew of new functions like “NewOpenWindow.” Then you come out with another revision and you want to add still more parameters.

When it gets silly enough, you look for another solution. The Amiga guys solved this problem with taglists. Taglists were much like an object–a list of names and corresponding values. You only supply the parameters you want to.

That was the first time I saw this sort of solution. They added an OpenWindowTags() function and that solved the problem forever. Unfortunately, in the case of the Amiga, “forever” didn’t last very long.

It’s a great solution for APIs of JavaScript libraries. You can add new stuff in the future without breaking any old calls to your library.

A good rule of thumb is to use JavaScript’s normal parameter conventions whenever the number of parameters are small and it’s inconceivable that they might change. Otherwise, pass in an object.