Default Function Parameter Values: JavaScript ES6 Feature Series (Pt 3)

Paige Niedringhaus
ITNEXT
Published in
8 min readAug 23, 2019

--

Less Parameter Undefined Checks Make Developers’ Lives Easier

Introduction

The inspiration behind these posts is simple: there are still plenty of developers for whom, JavaScript makes no sense — or at the very least, is bewildering at times.

Since JS does power so much of the web, I wanted to provide a slew of articles about JavaScript ES6 features that I use regularly, for other developers to reference.

The aim is for these articles to be short, in-depth explanations about various improvements to the language, that I hope will inspire you to write some really cool stuff using JS. Who knows, you might even learn something new along the way. 😄

For my third post, I wanted to talk about default function parameter values: the things that will save us all from tons of undefined checks in our code.

Traditional Function Parameters

As one of my previous posts detailed, functions are one of the fundamental building blocks of JavaScript. One of the things functions allow us to do is define a set of statements that performs a task or calculates a value the same way, every time, AND supply a list of parameters that can be passed in to be operated on.

Regardless of what values are passed in as parameters, the function will attempt to run the same tasks every time it is called.

Anatomy of a Function Declaration with Parameters

function simpleAdd(a, b){
return a + b;
}
simpleAdd(2, 3); // prints: 5

If you’re looking at the function declaration above, here’s what composes it. simpleAdd is the name of the function, a and b are the two parameters the function takes in, and the body of the function return a + b; is the statement.

Ok, so that’s simple, straightforward and makes perfect sense, right? Right. But what happens if a traditional function declaration isn’t passed the correct amount of parameters?

An overabundance of parameters is OK, the JavaScript engine just knows to ignore additional arguments that it doesn’t use in the function, but not supplying enough parameters is a different problem entirely… Check out what happens there.

Function Declaration with Too Many & Too Few Parameters

function add(a, b){
return a + b;
}
console.log(add(1, 2, 3, 4)); // prints: 3console.log(add(1)); // prints: NaN

Uh-oh, the lack of a b parameter didn’t turn out so well for the add(1) function. NaN (“not a number”) is something a developer never wants to see when attempting to do math with JavaScript.

Why did this happen? Because traditionally, function parameters default to undefined when the function is declared, and it waits for arguments to be passed in when the function is called in the code. So, how do we avoid dreaded things like NaN? Up until ES6, we had to do things like undefined checks everywhere in functions.

Checking a Function for `undefined` Parameters

function addImproved(a, b) {
if(a === undefined){
a = 3;
}
if(b === undefined){
b = 7;
}
return a + b;
}
console.log(addImproved(6,9)); // prints: 15 console.log(addImproved()); // prints: 10

In the example above addImproved(), in order to avoid undefined parameter errors, we have to physically check each parameter as it’s passed in and make sure that it is not undefined before finally letting the function get to its main goal: adding the two numbers together.

What a pain — imagine having to write this same type of logic over and over again for hundreds of functions throughout a large code base. 😩 No thanks, there’s got to be a better way…

Default Function Parameters

Enter the default function parameters; they allow named parameters in a function to be initialized with default values if no value or undefined is passed in.

As I briefly explained above, up until now, in JavaScript, function parameters automatically default to undefined; which meant we had to test parameter values in a function body, check for potentially undefined parameters and assign values if they were undefined.

Here’s an example of two ways to set values if they aren’t assigned. The first is a simple if / else statement, the second is a ternary statement checking if the parameter’s type is undefined or not, and setting the value if need be.

Two Old Ways of Preventing Undefined Parameters

function oldSum (x, y, z) {
if (y === undefined) {
y = 7;
}
z = (typeof z !== 'undefined') ? z : 42;
return `oldSum(${x}) === ${x + y + z}`;
};
console.log(oldSum(1)); // prints: oldSum(1) === 50
console.log(oldSum(2, 5, 8)); // prints: oldSum(2) === 15
console.log(oldSum()); // prints: oldSum(undefined) === NaN

Seems like a lot of extra code just to prevent errors before the function even runs. With default parameters in ES2015, checks in the function body are no longer necessary.

ES6 Way to Prevent Undefined Errors with Default Parameters

function newSum (x, y = 7, z = 42) {
return `newSum(${x}) === ${x + y + z}`;
};
console.log(newSum(1)); // prints: newSum(1) === 50
console.log(newSum(3, 6, 9)); // prints: newSum(3) === 18
console.log(newSum(16, undefined)); // prints: newSum(16) === 65

Much nicer! Right where the parameters are named, they are also given default values in case not enough arguments are supplied. It’s so much cleaner and easier to read this way, and removes the boilerplate of undefined checks.

I should also mention, the same rules that apply to default parameters in function declarations, work in arrow functions, as well. Check this out.

ES6 Default Parameters in Arrow Functions

const thisWayWorksToo = (x = 7, y = 8) => {
return x + y;
}
console.log(thisWayWorksToo(3, 4)); // prints: 7console.log(thisWayWorksToo()); // prints: 15

These types of examples are far and away the most common use cases for default parameters, however, there’s more to know about them, if you’re curious. 🤔

Gotchas and Other Use Cases For Default Parameters

Default params have some other interesting functionalities it will be helpful to know — they also have some really handy use cases. Let’s go over them so you don’t get tripped up, or when you do, you can more quickly debug the source of the problem.

Passing Undefined vs Other Falsy Values

One thing that could trip you up: passing other falsy values like null or '’ to a function with default parameters will not result in the default value being substituted instead.

function luckyNumber(num = 11) { 
console.log(typeof num);
};
luckyNumber(); // prints: 'number' (num is set to 11)
luckyNumber(undefined); // prints: 'number' (num is set to 11 too)
// test with other falsy values:luckyNumber(''); // prints: 'string' (num is set to '')
luckyNumber(null); // prints: 'object' (num is set to null)

As you can see above when luckyNumber() is called with no value or a value of undefined it falls back to its default param of 11. When it’s called, however, with an empty string or a null value, it takes that value instead.

Evaluation at Call Time

The second thing to know is: default values are evaluated at call time, so a new object is created each time the function is called — not, contrary to what you might think, adding to an already existing object or array.

function append(value, array = []) {
array.push(value);
return array;
}
console.log(append(1)); // prints: [1]
console.log(append(2)); // prints: [2], not [1, 2]

Each time the append() function runs, it creates a brand new array with the value supplied as its contents. The first time the function is called and executed it runs, makes an array and is done, and its execution context is destroyed once it’s done running.

What this means is that the function is now completely done with (and unaware of) the original array it created. Which is why, when append() is invoked a second time, with a second value, it creates a second, entirely new array, instead of adding to the first one it created.

Earlier Params are Available to Later Default Params

Ok, here’s a cool trick: parameters declared earlier (to the left) in a parameter string are then available to be used by later default parameters.

function welcome(name, greeting, message = greeting + ' ' + name) {
return [name, greeting, message];
}
console.log(welcome('Sean', 'Hi')); // ["Sean", "Hi", "Hi Sean"]
console.log(welcome('Sean', 'Hi', 'Happy Birthday!')); // ["Sean", "Hi", "Happy Birthday!"]

By passing in values for the name and greeting parameters, the third param of message is able to take those two to make another string value. Pretty sweet.

Imagine how much more cleanly you could handle functions with lots of potential variables. This example, which I borrowed from the MDN docs on default parameters really drives the point home.

Cleaner Edge Case Handling with and without Default Parameters

function go() {
return ':P';
}

function withDefaults(a, b = 5, c = b, d = go(), e = this,
f = arguments, g = this.value) {
return [a, b, c, d, e, f, g];
}

function withoutDefaults(a, b, c, d, e, f, g) {
switch (arguments.length) {
case 0:
a;
case 1:
b = 5;
case 2:
c = b;
case 3:
d = go();
case 4:
e = this;
case 5:
f = arguments;
case 6:
g = this.value;
default:
}
return [a, b, c, d, e, f, g];
}

withDefaults.call({value: '=^_^='});
// [undefined, 5, 5, ":P", {value:"=^_^="}, arguments, "=^_^="]

withoutDefaults.call({value: '=^_^='});
// [undefined, 5, 5, ":P", {value:"=^_^="}, arguments, "=^_^="]

Yeah, is there anyone who would choose to write out the withoutDefaults() function instead of the withDefaults() function? I think not.

Parameters Without Defaults after Default Parameters

While earlier params are available to later default params, you cannot, pass in defined parameters to fill the place of later undefined parameters in a function. Makes perfect sense, right? 😉 To put it another way: parameters are always set left-to-right, and they overwrite default parameters even if there are later parameters without defaults. This example below probably illustrates it better.

function f(x = 1, y) {
return [x, y];
}
console.log(f()); // prints: [1, undefined]console.log(f(4)); // prints: [4, undefined]

And those are some of the main things to know when it comes to default parameters in functions.

Conclusion

Even though JS has been around for more than 20 years and ES6 has been out since 2015, there’s still plenty of misinformation and knowledge gaps surrounding it.

I’m trying to demystify it and give you a better understanding of JavaScript, ES6 — things you may use everyday but not fully grasp the nuances of.

Functions are one of the key building blocks of JavaScript, and before now, checking for undefined parameters was just a fact of life. With the introduction of default parameters, though, at least some of the boilerplate can be peeled away, making functions easier to write, read and maintain, and I, for one, am incredibly grateful, and excited for future improvements as well.

Check back in a few weeks, I’ll be writing about more JavaScript and ES6 or something else related to web development, so please follow me so you don’t miss out.

Thanks for reading, I hope you’ll have a chance to take advantage of default parameters in the functions you write going forward, be they declarations, expressions or arrow functions. Please share this with your friends if you found it helpful!

If you enjoyed reading this, you may also enjoy some of my other blogs:

--

--

Staff Software Engineer at Blues, previously a digital marketer. Technical writer & speaker. Co-host of Front-end Fire & LogRocket podcasts