How to set default parameter values in JavaScript functions
When creating a function we often want to make sure that the values (called arguments) passed to the function when called has default values in case the user does not provide one, or provides the wrong one.
There are many ways to set default values, but here are five common ways:
- If statement
- Tenary expression
- Short-circuit evaluation
- Object.assign
- ECMAScript 2015 (ES2015) default parameters
When we call a function without passing a value for one of the parameters (the names used when declaring a function) JavaScript will instead pass the value undefined
as a default value for this parameter.
The three first methods overrides the JS default value with our own default value by detecting this undefined
value inside the function, and instead replacing it with a new value, while the last two sets their own default values initially instead of relying on the JS undefined
.
If statement (method 1)
The first and perhaps most intuitive way to set default values is with a simple if
statement.
The syntax should be familiar.
if (test expression)
statement
Using this we can set a default value for a variable like so:
function increment(num) {
if (!num) { num = 0 }
return ++num
}
increment() // returns 1 since "num" is set to 0 before incrementation
increment(22) // returns 23
If no value is passed for the num
parameter, num
will be set to the number 0
.
If the value of num
is set to undefined
if no value is passed for num
, then why do we write if(!num)
instead of if (num === undefined)
? The answer is that they both work, its just that !num
is shorter and faster to write, so we prefer it for this reason. This does come at a cost though. num === undefined
would only allow the value undefined
to enter the if
, setting the default value only in the case the value is undefined
, while !num
allows any value that is seen as false (called falsy value) by JS to enter the if
and set the default value. This makes the value undefined
enter the if, but also make values like 0
, ""
, NaN
enter the if and set default value, since they are all also seen as falsy values. If we do not want these values to be replaced by a default value, we must use num === undefined
instead.
Tip:
If you want to know which values are seen as false (read: converted too) by JS, you can do so with either !!value
or Boolean(value)
.
Boolean(20) // true
!!20 // true
Boolean(0) // false
!!0 // false
We can make the code a bit less verbose by skipping the brackets.
function increment(num) {
if (!num) num = 0
return ++num
}
This works because brackets are only necessary when we have several statements inside the if
statement. Brackets denote a block statement - a statement used to group other statements.
Tenary expression (method 2)
The second method, a tenary expression, can be used to set default values the same way as an if
statement.
The syntax of a tenary expression looks like this:
test expression ? value expression : value expression
If the test expression evaluates to true, the lefthand side of the semicolon is returned, else the righthand side value is returned.
The if
statement example we used above can be re-written as a tenary expression instead.
function increment(num) {
num = num ? num : 0
return ++num
}
If we pass a value for num
(a value not seen as falsy), num
will be assigned itself, else num
will be assigned 0
.
In this way, the left side of :
equals the if
statement and the right side equals the else
statement in an if else
statement.
To better exemplify this relationship: this tenary expression and if statement is identical in behaviour.
num = num ? num : 0
if (num) {
num = num
}
else {
num = 0
}
Tenary expressions do not create less code than an if
statement. However, when used as a replacement for an if else
statement, it creates shorter syntax, and also fits a single line better.
The bottom line is that an if
statement can achieve everything a tenary expression can, and the choice between the two mostly comes down to which feels more eloquent
in the given situation and the preference of the programmer. For this reason, you will encounter tenary expressions used, so its necessary to know them even if you don't use them personally.
Short-circuit evaluation (method 3)
A third method for assigning default values to parameters is called short-circuit evaluation. They are expressions
, just like tenary expressions.
These expressions make use of the ||
operator
and how this operator is seen and evaluated by JavaScript.
The syntax for such an expression is simple and looks like this:
value expression || value expression
To use the ||
operator to set default values we can do as in the following code snippet.
function increment(num) {
num = num || 0
return ++num
}
Just like in the previous examples, this assigns the default value 0
to num
if num
is undefined (or any value seen as falsy).
How it works
First, JS converts the value on the lefthand side to a boolean. If the result of this conversion is the boolean true
JS returns the value on the left. If, however, the result of this conversion is the Boolean false
, it returns the value on the righthand side instead.
From this we gather two important points:
- The expression does not return a bool. It returns the value on either the left or right side. The boolean is only used for evaluation purposes.
- The result is always just one of the values of either side
The name short-circuit comes from the second point. If the lefthand side converts to true
the expression "short-circuits", meaning it never goes to the right side.
Note: Using the && operator also works for selectively returning one of the sides. However, it does not work for assigning default values. This is because the righthand side (the default) value would be applied when the argument is true, not false, meaning everytime the argument is actually passed, not when it's not passed.
A flaw
It's tempting to use short-circuit evaluation due to its very short and easy syntax. Unfortunately, though, the reason for this short syntax, is also the cause of a problem. In a short-circuit expression the test and return value is intrinsically tied together.
both test expression and returned value || returned value
By contrast, in a tenary expression, we can see that these are separate, and that the value that the test expression evaluates to, does not affect the value returned.
test expression ? returned value : returned value
The consequence of this tying together is that we cannot change the lefthand side from e.g. arg || 0
to arg === "number" || 0
, since this would return a boolean if true instead of the value stored in arg
. In other words, the argument test and the returned value needs to be the same.
So in short. Short-circuit expressions are only useful when we want to assign the argument itself or a default value. Not, for instance, when we want to check if the value is a specific value (arg === undefined
) or of a specific datatype (typeof arg === "number"
), before choosing to assign a default value. In that case we need to use another method, like so: typeof arg === "number" ? arg : 0
.
Object.assign (method 4)
Object.assign
is a function that exists in many environments (e.g. chrome, firefox, node.js).
Object.assign(targetObject, sourceObject1, sourceObject2, etc..)
It assigns the properties of one or many objects (source objects), to a target object.
How it works
What rules does the function follow when assigning to the target object?
In short what happens is that:
- If the property exists in the target object and source object, it replaces the value of the target object property.
- If the property exists in the source object only, it creates/adds it, with the value, to the target.
- If the property does not exists in source, we dont do anything.
The following code illustrates all of these rules in effect:
// create the object we want to assign TO
var target = {name: "Ben", age: 29}
// create the object we want to assign FROM
var source = {name: "Hans", lastname: "Gruber"}
// assign the properties of source to target
Object.assign(target, source)
// This changes the target object from
// {
// name: "Ben",
// age: 29
// }
//
// to:
// {
// name: "Hans", // same key, but new value
// age: 29, // same key and value
// lastname: "Gruber" // a new property entirely
// }
As you might have noticed, if the user provides an (source/config) object with properties not present in the target
object those properties are added to the defualt object as well, however, this does not cause an issue, since any unnecessary properties added is not referenced anywhere in the function, causing them to just be treated like they dont exists.
Setting default values with Object.assign
A common use-case for assign
is when we want to set default configuration. If a function
executes a process and we want to customize how this process is run, we create a default object in
the function which controls this process and then we override any values of the default config object with the user provided object.
We can illustrate the use of Object.assign
to set default values by creating a tooltip that is displayed a certain way, but lets the user (function caller) have the final say in how it's displayed.
function showTooltip(user_config) {
// create values that will be used by default if no values are passed
var default_config = {
direction: "topleft",
backgroundColor: "#000"
}
// override some of the values in the default_config object
// with user preferences (user_config)
Object.assign(default_config, user_config)
// some imaginary function that displays a tooltip using the
// values in default_config
displayTooltip(default_config)
}
// shows a tooltip, using the default direction ("topleft"),
// but user preferred background color ("#ddd")
showTooltip({backgroundColor: "#ddd"})
The difference with this method compared to the others, is that we need to predefine default values from the get go, and then we replace the values if need be. In the other methods we only applied the new values if the user did not provide a value, or a wrong value. Using this method we assume values will not be passed, in the others we assumed they will.
Your own version
Using Object.assign
depends on whether it exists in
the environment that you are using (chrome, node etc), and the environments version (10, 11 etc).
If Object.assign
is not available you can make your own:
function assign(target, varArgs) {
var obj = Object(target)
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
obj[nextKey] = nextSource[nextKey];
}
}
}
}
return obj
}
This is a simplified version of Object.assign
that does not emulate its exact behavior, but does the trick most of the time. If you want a more elaborate version that behaves identically to Object.assign
, you can copy the function found here into your own code.
Note: To create a function with the exact same behaviour as Object.assign
(called a polyfill) it's necessary to use another function called Object.defineProperty
.
This function does not exists in some older browsers (and cannot be polyfilled itself), this means that, even though the linked function is superior to the simplified function in that it has identical behaviour to Object.assign
, it works in less browsers than the aforementioned. This is not really a big deal though since the support is still pretty good, and the simplified version does not work in all version either, even though it has better support.
ECMAScript 2015 (ES2015) default parameters (method 5)
Since the JavaScript version released in 2015 another method for setting default parameters values have existed simply named "default parameters".
The previous methods set default values inside the function. They let JavaScript pass undefined
as the default value to the function, and then we used an if statement, tenary or short-circuit expression to override the undefined
value to whatever we wanted instead.
With default parameters, however, we decide what value will be passed instead of undefined
, so we dont need to override the value
inside the function.
The syntax for setting default parameter values looks like this:
function fn(param1 = value expression, param2 = value expression, etc...) {
// ... code
}
Using this syntax we can set default values for parameters like this:
function add(a = 0, b = 10 - 10) {
return a + b
}
If nothing is passed for either a
or b
(or the value passed is undefined
), both a
and b
will
be set to the number 0
(b = 10 - 10 = 0), resulting in return 0 + 0
.
Default values can also be stored in variables:
var age = 20
function increment(num = age) {
return ++num
}
increment() // returns the number 20
In summary, using this more modern method is a very easy way to set default values. However, it isn't that commonly encountered; due to a combination of less browser support, slow adoptation by developers and the fact that much of the JavaScript code you encounter is already transpiled by babel (transformed into an older JavaScript version for better browser support) before you read it.
Conclusion
We have explored five ways to set default function values if the function caller does not provide, or provides the wrong, value.
Each method has its own strengths and weaknesses. For instance, using an if statement would offer the best readability, but the short-circuit method would produce the shortest syntax - tempting for when you set default values often, but insufficient if you need to explicitly check what the passed value is before choosing to assign a default value.
In the end though, which one you choose is mostly down to preference, since each option will do the job in most scenarios.