Using Default Parameters in ES6

Avatar of Louis Lazaris
Louis Lazaris on (Updated on )

I’ve recently begun doing more research into what’s new in JavaScript, catching up on a lot of the new features and syntax improvements that have been included in ES6 (i.e. ES2015 and later).

You’ve likely heard about and started using the usual stuff: arrow functions, let and const, rest and spread operators, and so on. One feature, however, that caught my attention is the use of default parameters in functions, which is now an official ES6+ feature. This is the ability to have your functions initialize parameters with default values even if the function call doesn’t include them.

The feature itself is pretty straightforward in its simplest form, but there are quite a few subtleties and gotchas that you’ll want to note, which I’ll try to make clear in this post with some code examples and demos.

Default Parameters in ES5 and Earlier

A function that automatically provides default values for undeclared parameters can be a beneficial safeguard for your programs, and this is nothing new.

Prior to ES6, you may have seen or used a pattern like this one:

function getInfo (name, year, color) {
  year = (typeof year !== 'undefined') ? year : 2018;
  color = (typeof color !== 'undefined') ? color : 'Blue';
  // remainder of the function...
}

In this instance, the getInfo() function has only one mandatory parameter: name. The year and color parameters are optional, so if they’re not provided as arguments when getInfo() is called, they’ll be assigned default values:

getInfo('Chevy', 1957, 'Green');
getInfo('Benz', 1965); // default for color is "Blue"
getInfo('Honda'); // defaults are 2018 and "Blue"

Try it on CodePen

Without this kind of check and safeguard in place, any uninitiated parameters would default to a value of undefined, which is usually not desired.

You could also use a truthy/falsy pattern to check for parameters that don’t have values:

function getInfo (name, year, color) {
  year = year || 2018;
  color = color || 'Blue';
  // remainder of the function...
}

But this may cause problems in some cases. In the above example, if you pass in a value of 0 for the year, the default 2018 will override it because 0 evaluates as falsy. In this specific example, it’s unlikely you’d be concerned about that, but there are many cases where your app might want to accept a value of 0 as a valid number rather than a falsy value.

Try it on CodePen

Of course, even with the typeof pattern, you may have to do further checks to have a truly bulletproof solution. For example, you might expect an optional callback function as a parameter. In that case, checking against undefined alone wouldn’t suffice. You’d also have to check if the passed-in value is a valid function.

So that’s a bit of a summary covering how we handled default parameters prior to ES6. Let’s look at a much better way.

Default Parameters in ES6

If your app requires that you use pre-ES6 features for legacy reasons or because of browser support, then you might have to do something similar to what I’ve described above. But ES6 has made this much easier. Here’s how to define default parameter values in ES6 and beyond:

function getInfo (name, year = 2018, color = 'blue') {
  // function body here...
}

Try it on CodePen

It’s that simple.

If year and color values are passed into the function call, the values passed in as arguments will supersede the ones defined as parameters in the function definition. This works exactly the same way as with the ES5 patterns, but without all that extra code. Much easier to maintain, and much easier to read.

This feature can be used for any of the parameters in the function head, so you could set a default for the first parameter along with two other expected values that don’t have defaults:

function getInfo (name = 'Pat', year, color) {
  // function body here...
}

Dealing With Omitted Values

Note that—in a case like the one above—if you wanted to omit the optional name argument (thus using the default) while including a year and color, you’d have to pass in undefined as a placeholder for the first argument:

getInfo(undefined, 1995, 'Orange');

If you don’t do this, then logically the first value will always be assumed to be name.

The same would apply if you wanted to omit the year argument (the second one) while including the other two (assuming, of course, the second parameter is optional):

getInfo('Charlie', undefined, 'Pink');

I should also note that the following may produce unexpected results:

function getInfo (name, year = 1965, color = 'blue') {
  console.log(year); // null
}
getInfo('Frankie', null, 'Purple');

Try it on CodePen

In this case, I’ve passed in the second argument as null, which might lead some to believe the year value inside the function should be 1965, which is the default. But this doesn’t happen, because null is considered a valid value. And this makes sense because, according to the spec, null is viewed by the JavaScript engine as the intentional absence of an object’s value, whereas undefined is viewed as something that happens incidentally (e.g. when a function doesn’t have a return value it returns undefined).

So make sure to use undefined and not null when you want the default value to be used. Of course, there might be cases where you want to use null and then deal with the null value within the function body, but you should be familiar with this distinction.

Default Parameter Values and the arguments Object

Another point worth mentioning here is in relation to the arguments object. The arguments object is an array-like object, accessible inside a function’s body, that represents the arguments passed to a function.

In non-strict mode, the arguments object reflects any changes made to the argument values inside the function body. For example:

function getInfo (name, year, color) {
  console.log(arguments);
  /*
  [object Arguments] {
    0: "Frankie",
    1: 1987,
    2: "Red"
  }
  */
  
  name = 'Jimmie';
  year = 1995;
  color = 'Orange';

  console.log(arguments);
  /*
  [object Arguments] {
    0: "Jimmie",
    1: 1995,
    2: "Orange"
  }
  */
}

getInfo('Frankie', 1987, 'Red');

Try it on CodePen

Notice in the above example, if I change the values of the function’s parameters, those changes are reflected in the arguments object. This feature was viewed as more problematic than beneficial, so in strict mode the behavior is different:

function getInfo (name, year, color) {
  'use strict';

  name = 'Jimmie';
  year = 1995;
  color = 'Orange';

  console.log(arguments);
  /*
  [object Arguments] {
    0: "Frankie",
    1: 1987,
    2: "Red"
  }
  */
}
  
getInfo('Frankie', 1987, 'Red');

Try it on CodePen

As shown in the demo, in strict mode the arguments object retains its original values for the parameters.

That brings us to the use of default parameters. How does the arguments object behave when the default parameters feature is used? Take a look at the following code:

function getInfo (name, year = 1992, color = 'Blue') {
  console.log(arguments.length); // 1
  
  console.log(year, color);
  // 1992
  // "Blue"

  year = 1995;
  color = 'Orange';

  console.log(arguments.length); // Still 1
  console.log(arguments);

  /*
  [object Arguments] {
    0: "Frankie"
  }
  */
  
  console.log(year, color);
  // 1995
  // "Orange"
}

getInfo('Frankie');

Try it on CodePen

There are a few things to note in this example.

First, the inclusion of default parameters doesn’t change the arguments object. So, as in this case, if I pass only one argument in the functional call, the arguments object will hold a single item—even with the default parameters present for the optional arguments.

Second, when default parameters are present, the arguments object will always behave the same way in strict mode and non-strict mode. The above example is in non-strict mode, which usually allows the arguments object to be modified. But this doesn’t happen. As you can see, the length of arguments remains the same after modifying the values. Also, when the object itself is logged, the name value is the only one present.

Expressions as Default Parameters

The default parameters feature is not limited to static values but can include an expression to be evaluated to determine the default value. Here’s an example to demonstrate a few things that are possible:

function getAmount() {
  return 100;
}

function getInfo (name, amount = getAmount(), color = name) {
  console.log(name, amount, color)
}

getInfo('Scarlet');
// "Scarlet"
// 100
// "Scarlet"

getInfo('Scarlet', 200);
// "Scarlet"
// 200
// "Scarlet"

getInfo('Scarlet', 200, 'Pink');
// "Scarlet"
// 200
// "Pink"

Try it on CodePen

There are a few things to take note of in the code above. First, I’m allowing the second parameter, when it’s not included in the function call, to be evaluated by means of the getAmount() function. This function will be called only if a second argument is not passed in. This is evident in the second getInfo() call and the subsequent log.

The next key point is that I can use a previous parameter as the default for another parameter. I’m not entirely sure how useful this would be, but it’s good to know it’s possible. As you can see in the above code, the getInfo() function sets the third parameter (color) to equal the first parameter’s value (name), if the third parameter is not included.

And of course, since it’s possible to use functions to determine default parameters, you can also pass an existing parameter into a function used as a later parameter, as in the following example:

function getFullPrice(price) {
  return (price * 1.13);
}

function getValue (price, pricePlusTax = getFullPrice(price)) {
  console.log(price.toFixed(2), pricePlusTax.toFixed(2))
}

getValue(25);
// "25.00"
// "28.25"

getValue(25, 30);
// "25.00"
// "30.00"

Try it on CodePen

In the above example, I’m doing a rudimentary tax calculation in the getFullPrice() function. When this function is called, it uses the existing price parameter as part of the pricePlusTax evaluation. As mentioned earlier, the getFullPrice() function is not called if a second argument is passed into getValue() (as demonstrated in the second getValue() call).

Two things to keep in mind with regards to the above. First, the function call in the default parameter expression needs to include the parentheses, otherwise you’ll receive a function reference rather than an evaluation of the function call.

Second, you can only reference previous parameters with default parameters. In other words, you can’t reference the second parameter as an argument in a function to determine the default of the first parameter:

// this won't work
function getValue (pricePlusTax = getFullPrice(price), price) {
  console.log(price.toFixed(2), pricePlusTax.toFixed(2))
}

getValue(25); // throws an error

Try it on CodePen

Similarly, as you would expect, you can’t access a variable defined inside the function body from a function parameter.

Conclusion

That should cover just about everything you’ll need to know to get the most out of using default parameters in your functions in ES6 and above. The feature itself is quite easy to use in its simplest form but, as I’ve discussed here, there are quite a few details worth understanding.

If you’d like to read more on this topic, here are some sources: