Javascript landed with native default parameters support with ES2015. Contrary to what one might think, it isn’t just a syntactic replacement for||
(the logical OR operator).
Javascript before default parameters
Before the native support for default parameters, one would write something like this:
function filterEvil(array, evil) {
evil = evil || 'darth vader';
return array.filter(item => item !== evil);
}
To understand the code snipped above, one needs to keep the following things in mind:
-
Truthy/Falsy: Values in javascript can be categorized into truthy or falsy. In simple words, Javascript tries to convert a variable into a boolean value, to figure out the conditionals (eg. if, for loop). * Short Circuit Evaluation: The expression involving
||
is processed left to right. While processing left to right, the compiler is smart enough to not process the remaining items if it encounters a truthy value aka Short Circuiting.
In short, this is just a syntactic sugar for:
function filterEvil(array, evil) {
if (!evil) {
evil = 'darth vader';
}
return array.filter(item => item !== evil);
}
The reason this method is so prevalent is that you can chain multiple ||
’s and let short-circuiting take care of it. Imagine writing the same thing with if statements.
Now if your evil variable happens to be an empty string ''
or any falsy value, the function above will assume it to be darth vader
.
This can be okay in most cases, but it can cause those dreaded silent bugs in a more involving application. A great solution to this problem is within the language and it is called default parameters.
Javascript after default parameters
Let us use the same filterEvil example and see how it would look with default parameters.
function filterEvil(array, evil = 'darth vader') {
return array.filter(item => item !== evil);
}
At first glance, it looks neat and concise. But don’t let the looks deceive you! There is a lot going under the hood. It would be frivolous to assume that the default value would step in whenever evil
is not supplied.
Let us look at some important details,
1. Distinction between null and undefined.
Javascript has two answers to the not present problem, null
and undefined
. ( null
being a topic of controversy). The general consensus is that null
is an explicit value to tell that there is no value. In the example below, we try to pass null
as an argument.
const list = [ 'luke', 'leia', 'darth vader' ];
filterEvil(list, null); // [ 'luke', 'leia', 'darth vader' ]
The filterEvil function will not substitute
evil
with a default value when supplied withnull
or any falsy value.
The filterEvil function will substitute if and only if evil
is undefined
. Now you are in luck, as Javascript will automatically use undefined
if you do not explicitly pass an argument:
const list = [ 'luke', 'leia', 'darth vader' ];
filterEvil(list); // [ 'luke', 'leia' ]
This is something to keep in mind when working on projects which rely heavily on null
.* (Although, I worry a lot about the developers who use null
/undefined
interchangeably)*
2. Evaluated Left to Right
The default parameters are evaluated from left to right. This is indeed confusing but a very powerful feature. Let us look at an example.
function findEvil(array, evil = 'darth vader', respect = 'Bad ' + evil) {
if (array.find(item => item === evil)) {
return respect;
}
}
findEvil(list); // Bad darth vader;
findEvil(list, 'luke'); // Bad luke;
As you can see, we can reuse a param on the left as a default param for something on the right. Note that respect
will get the evil
param with the default check applied.
3. Calling a function
You can also call a function and use the value returned as a default parameter. In short this lets you call a regular function and compute the default parameter on the fly.
function whoIsEvilNow() {
if (time > 2014) {
return 'J. J. Abrams'
}
return 'darth vader';
}
function findEvil(array, evil = whoIsEvilNow()) {
return array.find(item => item === evil);
}
4. Evaluated at call time
Now this feature is what confuses me the most. Let us look at an example. Confusing but a very powerful feature. Let us look at an example.
function filterEvil(array = [], evil = 'darth vader') {
return array.filter(item => item === evil)
}
Each time you call an array without an argument, a new instance of an empty array is created.
Each time you call filterEvil without an argument, a new instance of an empty array is created. Now, this might become a problem if you are into into selectors and memoization. This behaviour can easily fool your dependant logic thinking something changed (I am talking to you React). For example, if you use reselect in your react project, your component can update unnecessarily as react will re-render with each new instance of the empty array.
Here are some cool tricks:
- Required Param Check: You can use default params to enforce a required parameter. In the example below, we are enforcing evil as a required prop.
const isRequired = () => { throw new Error('param is required'); };
function filterEvil(array, evil = isRequired()) {
return array.filter(item => item !== evil);
}
- Destructured Default Params: You can also use default params in a destructed expression.
function firstItem([first, second] = ['luke', 'skywalker']) {
return first;
}
function findName({ name } = { name : 'darth' }) {
return name;
}
I hope this post helped you in understanding default parameters.
Reach out to me on Twitter @kushan2020.
Top comments (1)
Wonderful summary. Lots of things I didn't know about, especially that required param trick.