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"
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.
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...
}
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');
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.
arguments
Object
Default Parameter Values and the 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');
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');
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');
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"
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"
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
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:
- Understanding ECMAScript 6 by Nicholas Zakas. This was my primary source for this article. Nicholas is definitely my favorite JavaScript author.
- Arguments object on MDN
- Default Parameters on MDN
Chrome actually supported this before it was part of any standard. I used the feature without knowing it was Chrome specific, because it always seemed like something every language should have and it seemed to work. Then I discovered IE wasn’t working and I had a hell of a time figuring it out until a co worker noted that what I was using technically wasn’t valid JavaScript, even though Chrome made it work. Glad to see it’s becoming official. The typeof pattern at the start of functions always felt so inelegant.
Shouldn’t the first code example under #default-parameter-values-and-the-arguments-object log this after changing the values, as it does on CodePen?
Yes, fixed now. Sorry, I think that was a copy-paste error like another one that was pointed out.
Looks like a mis-paste?
getValue(25, 30);
// “25.00”
// “28.25” — should be 30.00
Yes, it’s fixed. Thank you!
You skipped on default named params with object destructuring.
Or perhaps a more practical example:
Nice, thanks. I haven’t looked into destructuring much (only superficially) so I definitely wouldn’t be able to speak much on that subject. But thanks for adding it to the discussion.
nice information btw ! super usefull
Before default params were a thing, I really preferred
Since its slightly more elegant, and
!=
checks for undefined as well as null (though it’s up to your discretion if you only care about undefined) and it doesn’t have the falsy value issue||
does.Some of the expected outputs written as comments in the snippets are wrong, you might want to check again.
Thanks. It was just the comments in the demos, they are all correct now. Mostly just copy-paste mistakes that were remnants of changed code in the live demo itself.