With the 2015 version of JavaScript, JavaScript functions can now accept default parameters in functions and there’s the rest operator to capture optional arguments in an array.
This is handy for handling optional parameters and there are many use cases for these features. Before we had these two features, all we had was the arguments
object with the list of arguments of the function that was called.
All we could do to prevent undefined errors for optional parameters was to check each argument directly to see if they were defined and then set the value directly.
This is painful for developers as it’s possible to have a long list of optional arguments that we want the function to get. With default parameters, the default value for parameters is set directly in the signature of the function. This eliminates many lines of code that check the code.
Default Parameters
Default parameters are easy with JavaScript. To set them in our code, we write the following:
const sum = (a,b=1) => a+b
In the code above, we set the default parameter value of b
to 1
, so if the second argument isn’t passed in when we call the sum
function, then b
is automatically set to 1
.
So, if we call sum
as follows, we get 2
returned:
sum(1)
As we can see, now we don’t have to worry about having optional arguments being undefined
, which would be the alternative result if no default value is set for parameters. This eliminates many sources of errors that occur when making parameters optional with JavaScript.
The alternative way, without using default parameters, would be to check if each parameter is undefined
. We write the following to do this:
const sum = (a,b,c)=>{
if (typeof b === 'undefined'){
b = 1;
}
if (typeof c === 'undefined'){
c = 1;
}
return a+b+c;
}
If we call the following sum
function without parameters, we get:
const sum = (a,b = 1,c = 1)=> a+b+c;
sum(1) // returns 3
sum(1, 2) // returns 4
As we can see, we handled missing parameters gracefully with default parameters.
For default parameters, passing in undefined
is the same as skipping the parameters. For example, if we call the following function and pass in undefined
, we get:
const sum = (a,b = 1,c = 1)=> a+b+c;
sum(1,undefined) // returns 3
sum(1,undefined,undefined) // returns 3
sum(1, 2,undefined) // returns 4
Note that undefined
is the same as skipping parameters.
This isn’t true for other falsy values that are passed into a function. So, if we pass in 0
, null
, false
, or empty string, they get passed in and the default parameter values will be overwritten.
For example, if we have the code below:
const test = (num=1) => typeof num;
test(undefined);
test(null);
test('');
test(0);
test(false);
We get number
for test(undefined)
, object
for test(null)
, string
for test(string)
, number
for test(0)
, and boolean
for test(false)
.
This means that anything other than undefined
is passed in, but note that if we pass in falsy values and then run arithmetic operations on them, falsy values get converted to 0
.
const sum = (a,b = 1,c = 1)=> a+b+c;
sum(1,null)
sum(1,null,false)
sum(1, 2,'')
So, sum(1, null)
returns 2
as b
is converted to 0
and c
has the default value of 1
.
sum(1)
returns 1
as b
and c
are converted to 0
. sum(1, 2,’’)
is 3
as a
is 1
, b
is passed in so that it becomes 2
instead of getting the default value of 1
, and c
is an empty string which is converted to 0
.
Default arguments are evaluated at call time, so that they’re set each time they’re called if no argument is passed into the function parameter with default values.
For example, if we have:
const append = (val, arr = [])=>{
arr.push(val);
return arr;
}
append(1);
append(2);
append(1)
returns [1]
and append(2)
returns [2]
as we didn’t pass in an array to each function call, so arr
is set to an empty array each time it’s run.
It’s also important to know that we can pass in values returned by functions in the default parameter, so if we have a function that returns something, we can call it in the default parameter and assign the returned value to the parameter.
For example, we can write:
const fn = () => 2
const sum(a, b = fn()) => a+b;
Then, if we call sum(1)
, we get 3
as the fn
function returns 2
. This is very handy if we want to manipulate and combine values beforehand, before assigning it as a parameter.
Another great feature of the default parameters is that the parameters left of the given parameter are available for the parameter for assignment as default values, so we can have a function like the function below:
const saySomething = (name, somethingToSay, message = `Hi ${name}. ${somethingToSay}`) => ({
name,
somethingToSay,
message
});
Notice that we assigned an expression to the message
parameter in the saySomething
function. This is great for manipulating data and then assigning as we did before by assigning the function.
We can also see that we can have default parameters that depend on parameters that are to the left of it. This means that default parameters do not have to be static.
So, we call it with the first two parameters filled in, like saySomething(‘Jane’, ‘How are you doing?’)
. We get:
{name: "Jane", somethingToSay: "How are you doing?", message: "Hi Jane. How are you doing?"}
The message
is returned with the template string that we defined evaluated.
We cannot call functions nested inside a function to get the returned value as the default parameter value. For example, if we write:
const fn = (a = innerFn())=>{
const innerFn = () => { return 'abc'; }
}
This will result in a ReferenceError
being thrown, because the inner function isn’t defined yet when the default parameter is defined.
We can also have default parameter values that are set to the left of required parameters. Arguments are still passed into parameters from the left to the right, so if we have:
const sum = (a=1,b) => a+b
If we have sum(1)
, we have NaN
return 1
. It is added to undefined
as we didn’t pas in anything for the second argument, so b
is undefined
.
However, if we write sum(1,2)
then 3
is returned as we have a
set to 1
and b
set to 2
.
Finally, we can use a destructuring assignment to set default parameter values. For example, we can write:
const sum = ([a,b] = [1,2], {c:c} = {c:3}) => a+b+c;
Then, we call sum
without arguments. We get 6
because a
is set to 1
, b
is set to 2
, and c
is set to 3
by the destructuring assignment feature that JavaScript provides.
Rest Operator
The rest operator is a JavaScript operator where we can store an indefinite number of arguments in a function as an array.
It looks exactly like the spread operator, which lets us spread entries of an array or a list of key-value pairs of an object into another object.
For example, if we have a function that has a long list of arguments, we can use the rest operator to shorten the list of parameters in a function.
The rest operator is used with the following syntax:
const fn = (a,b,..restOfTheArgs) => {...}
Where restOfTheArgs
is an array with the list of arguments other than the first two. For example, if we want to write a sum
function that takes an indefinite list of numbers as arguments and sum up the numbers, we can write:
const sum = (a,b,...otherNums) => {
return a + b + otherNums.reduce((x,y)=>x+y, 0);
}
As we can see, this is very handy for functions that have a list of arguments. Before we had this, we had to use the arguments
object available in functions to get a list of arguments.
This is not ideal, as we allow them to pass in anything into the arguments. With the rest operator, we have the best of both worlds. We can have some fixed parameters, while the rest stays flexible.
This makes functions more flexible than functions with a fixed number of parameters, while having some flexibility with functions that take an indefinite number of arguments.
The arguments
object has all the arguments of the function. Also, it’s not a real array, so array functions aren’t available to them. It’s just an object with indexes and keys to denote the argument’s positions.
Methods like sort, map, forEach, or pop cannot be run on the arguments
object. It also has other properties. This creates confusion for programmers.
The arguments that are converted to an array with the rest operator do not have these issues, as it is a real array.
To call the sum
function we wrote, we can write:
const result = sum(1,2,3,4,5,6,7,8,9,10);
result
will be 55
, as we summed up all the arguments together. otherNums
is an array with all the numbers other than 1 and 2.
We can also use the rest operator to destructure a list of arguments into a list of variables. This means that we can convert a list of parameters into an array with the spread operator, and then decompose the array of arguments into a list of variables.
This is very useful because we can get the entries of the array that’s operated on by the rest operator and convert them to named variables.
For example, we can write:
const sum = (a,b,...[c,d,e])=> a+b+c+d+e;
This way, we can use the rest operator but limit the number of arguments that your function accepts.
We take the function parameters a
and b
, and we also take c
, d
, and e
as parameters. However, it’s probably clearer without using the rest operator as all the parameters are fixed, we can just list the parameters directly.
Conclusion
Now that we have the 2015 version of JavaScript, we can do many handy things with parameters.
Default values and the rest operator are handy for handling missing or operation parameters. Default parameters let us set default values for parameters that might not have anything passed in in function call arguments.
The rest operator lets us retrieve some or all items listed in the argument as an array. Letting us manipulate them with array operations or using the destructuring assignment to decompose them into individual variables.