Put yourself in the place of the listener,
of the eater,
of the reader,
of the user of your carefully crafted spells,
Think from the outside in,
and you'll be rewarded manyfold.
Think developer experience!!
Have You Heard Of Defaults?
I don’t know you but I’m always trying to write less code and build beautiful APIs for myself and other developers. One small way to achieve that is by using default arguments. Defaults let you add more intention behind your APIs and provide a shortcut to the most common functionality or task carried out by a function.
In this and the next few chapters we’ll discuss several patterns that you can use to improve the usability of the functions you write in JavaScript: defaults, multiple arguments and function overloading. Let’s get started with defaults in ES5 and ES6.
Using Default Arguments in JavaScript Today
If you have had the chance to take a look at some Javascript code, you may have encountered the following statement and wondered what (the heck) it meant:
Hold on tight because we are about to unveil the mystery. That statement right there has been the prevalent approach to writing defaults in JavaScript for ages. If you do a little of a mental exercise and try to remember what you read about the || (OR) operator in the introduction you’ll recall that this operator behaves in quite a special way: It returns the first truthy expression of those being evaluated or the last expression if none is truthy. As usual, this behavior is better illustrated through an example:
Since up until ES6 there was no native support for defaults and using the || operator was the most compact way to achieve it, this pattern soon became the de facto standard for defaults throughout the community and is often used in JavaScript applications. You just need to take a sneak peak in any popular open source library to find it used in innumerable situations:
One of the most common use of defaults happens when evaluating the arguments of a function like in this castIceCone spell:
The castIceCone function has two arguments: a mana argument that represents the amount of mana a powerful wizard is going to spend in casting the ice cone and an additional options object with finer details.
The function makes extensive use of the || operator to provide defaults for all possible cases. In the simplest and most convenient of scenarios the user of this function would just call it directly, and when more finesse is needed she or he could populate the richer options argument:
An alternative way to do defaults is to wrap them within an object.
Defaults with Objects
In addition to relying on the || operator, you can use an object to gather your default values. Whenever the function is called you merge the arguments provided by the user with the object that represents the defaults. The default values will only be applied when the actual arguments are missing.
You can define a mergeDefaults function to perform the merge operation:
And then apply it to the arguments passed to the function like in this castLightningBolt spell:
Which provides a similar defaults developer experience to that of the first example:
More often though you will probably rely on a popular open source library. Libraries like jQuery, underscore or lodash usually come with a lot of utility functions that you can use for this and many other purposes. For instance, jQuery comes with the $.extend function and underscore comes with both the _.defaults and _.extend functions that could help you in this scenario.
Let’s update the previous example with code from these two libraries:
If you have kept an eye on ES6 you may know that it comes with a native version of the jQuery $.extend method called Object.assign. Indeed, you can update the previous example as follows and achieve the same result:
However, if you are planning to use ES6, there’s an even better way to use defaults.
Native Default Arguments with ECMAScript 6
ES6 makes it dead easy to declare default arguments. Just like in C# you use the equal sign "=" and assign a default value beside the argument itself:
JavaScript takes defaults even further because they are not limited to constant expressions like C# optional arguments. In JavaScript, any expression is a valid default argument.
For instance, you can use entire objects as defaults:
If you take a closer look at the end of the example above, you’ll realize that we have a small bug in our function. We are setting a default for the entire options object but not for parts of it. So if the developer provides an object with the direction property missing, we will get a strange result (writing undefined to the console).
We can solve this problem by taking advantage of the new destructuring syntax which allows you to assign argument properties directly to variables within a function, and at the same time provide defaults to parts of an object:
In this example we use argument destructuring {direction='forward'} to:
Extract the property direction from the argument provided to the function at once. This allows us to write direction instead of the more verbose options.direction that we used in previous examples.
Provide a default value for the direction property in the case that the function is called with a options object that misses that property. It therefore solves the problem with the {duck:'cuack'} example.
Finally, taking the freedom of defaults to the extreme, you are not limited to arbitrary objects either, you can even use a function expression as a default (I expect your mind has just been blown by this, this… very… second):
Concluding
Yey! In this chapter you’ve learned several ways in which you can use defaults in JavaScript whether you are using ES5 or ES6 and beyond. Taking advantage of defaults will let you write less code and provide a slightly better user experience to the consumers of the functions that you write.
Up next, more function patterns with multiple arguments and the rest operator!