Article content;
In this article, there are a lot of modern javascript concepts related to functions which include and not limited to;
- The use of rest parameters in functions,
- Use of the spread operator when calling a function
- Use of default parameters in functions
- Use of expressions as default function parameters
Most of the recommended javascript features in this article are based on newer javascript versions from es6.
Traditionally, a function is declared simply as follows;
function functionName(parameter1, parameter2, parameter3) {
// code to be executed
}
In an actual example for a function that takes two parameters and provides their sum, the flow would be as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function definition
const addition = function (num1, num2) { // num1, num2 - function parameters
//function operations
let sum = num1 + num2;
//return type
return sum;
};
//calling a function - addition(3, 5) - Argumetes
console.log(addition(3, 5)); //8
Visit this post that explains the dynamics of using const vs using let for functions, objects, and arrays definition.
Parameters vs arguments;
From the above example, num1 and num2 are the parameters while 3 and 5 are the arguments. The return type is basically the result of the function operations which in this case is the sum.
Now into some specifics;
1. passing a function vs calling a function;
There is a difference between calling a function and passing a function in javascript. Passing a function means the function becomes part of the parameters. Example; Let's have two functions, one to add two values while the other subtracts two values;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function 1 - add
function add(num1, num2) {
return num1 + num2;
}
console.log(add(5, 3)); //8
//function 2 -subtract
function subtract(num1, num2) {
return num1 - num2;
}
console.log(subtract(5, 3));//2
Having the two functions, it's tedious to choose or select the operation we need. The solution is to create another function to do this for us (calculate) and pass a function as one of its parameters (operation). as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function 1 - add
function add(num1, num2) {
return num1 + num2;
}
console.log(add(5, 3)); //8
//function 2 -subtract
function subtract(num1, num2) {
return num1 - num2;
}
console.log(subtract(5, 3));//2
// passing a function as a parameter
function calculate(num1, num2, operation) {
return operation(num1, num2);
}
// calling a function as an argument
console.log(calculate(5, 3, add));//8
console.log(calculate(5, 3, subtract));//2
All that the new function does is call any other calculating functions. These type of functions that take other functions as inputs are called HIGHER ORDER FUNCTIONS.
From this point on, it's possible to add more functionalities with different operations like multiplication as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function 1 - add
function add(num1, num2) {
return num1 + num2;
}
//function 2 -subtract
function subtract(num1, num2) {
return num1 - num2;
}
console.log(subtract(5, 3));//2
//function3 - multiply
function multiply(num1, num2) {
return num1 * num2;
}
// passing a function as a parameter
function calculate(num1, num2, operation) {
return operation(num1, num2);
}
// calling a function as an arguement
console.log(calculate(5, 3, add));//8
console.log(calculate(5, 3, subtract));//2
console.log(calculate(5, 9, multiply));//45
2. Constructor functions:
These are functions that create objects; Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
function Car(color, make, model, year, chases) {
this.color = color;
this.make = make;
this.model = model;
this.year = year;
this.chases = chases;
}
//create an object from the function as follows;
let car1 = new Car("black", "Mercedes", "c130", 2021, "chasis001");
console.log(car1);
The only difference when calling the function is the use of the keyword new and the function name is capitalized.
3. More arguments than parameters in a function;
This example illustrates what happens when there are more arguments than parameters in a function;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function definition
const addition = function (num1, num2) { // num1, num2 - function parameters
//function operations
let sum = num1 + num2;
//return type
return sum;
};
//calling a function - addition(3, 5,7) -arguments
console.log(addition(3, 5, 7)); //8
Javascript basically ignores the extra arguments (7) and just operates with the first two which means our return value is inaccurate.
4. Where parameters are more than arguments;
This example illustrates what happens when there are more parameters than the arguments in a function;
// jshint esversion:9
"use strict";
/* jshint node: true */
//function definition
const addition = function (num1, num2, num3) { // num1, num2,num3 - function parameters
//function operations
let sum = num1 + num2 + num3;
//return type
return sum;
};
//calling a function - addition(3,5) -arguments
console.log(addition(3, 5));
The extra parameters(num3) are deemed undefined and the sum/result is NaN (Not a number) which clearly is the unintended result.
5. Rest parameters vs Spread operator;
To mitigate the above scenarios, javascript es6 and later versions introduced two very effective functionalities to help work with parameters and arguments namely; the Rest parameters and the Spread operator.
The symbol used to identify the two is identical (…) but they are used at different instances of your javascript code. Rest parameter at the top of the functions as part of the parameters list while the Spread operator when calling the function specified as part of the arguments list.
Their functionality is the opposite of each other where rest is used to gather the parameters into an array, spread moves to split a collection into separate values. Rest parameter
- Rest parameter;
A rest parameter is defined using the ellipsis (…) which essentially indicates that the parameter can hold any number of variables. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const max = function (...values) {//Rest parameter
console.log(values instanceof Array); //true - of type Array
let large = values[0];
for (let i = 0; i < values.length; i++) {
if (values[i] > large) {
large = values[i];
}
}
return large; // returns the largest value
};
console.log(max(2, 3, 4, 15, 6, 7, 8));
This solves the issues that prevailed within the previous two examples of having more parameters than the arguments or vice versa. The rest parameter;
- Represents all parameters of the function,
- Is of type array and carries with it all array capabilities, which means it allows more complex operations which include compatibility with expressive functions explained later in this article.
The rest parameter works under some strict guidelines which include;
- It has to be the last parameter,
- There can be only one rest parameter in a function’s parameter list.
- It should only contain parameters with no explicit names.
The rest parameter is a very useful javascript feature especially when it comes to working with a variable number of parameters in functions. There are several ways the rest parameter can work with a set of arguments that include;
- Discrete argument values, Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const salute = function (...names) { // rest parameter
return 'Hello ' + names.join(', Hello '
);
};
console.log(salute("Cyrus ", "Node")); // discrete arguments
- Using arrays can also be another option;
// jshint esversion:9
"use strict";
/* jshint node: true */
const salute = function (...names) {//rest parameter
return 'Hello ' + names.join(', Hello '
);
};
const users = ["Cyrus ", "Node"]; //array
console.log(salute(users[0], users[1])); //Array based arguments
- Spread operator;
When it comes to working with arguments, there is the spread operator. This is a javascript addition specifically the es9 version. Instead of using either discrete or array arguments directly as arguments as elaborated in the examples above, using the spread operator is by far the best method when it comes to such a scenario. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const salute = function (...names) {//rest parameter
return 'Hello ' + names.join(', Hello '
);
};
const users = ["Cyrus ", "Node"]; //array of arguments
console.log(salute(...users)); //spread operator
This example demonstrates the true power of javascript’s rest parameter (…names) and the spread operator(…users) working together.
The function salute doesn’t have to receive a specific number of parameters but instead uses the rest parameter.
The function also doesn’t have to declare the number of arguments as they are all broken down from any size of the array to discrete values. This example demonstrates the use of the spread operator (…users) utilized when calling the function.
As already mentioned, the spread's functionality is the opposite of the Rest parameter - to break a collection of values into individual values. This functionality isn’t just limited to the functions but also to breaking arrays with multiple sets of data into individual values- Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const salute = function (name1, name2, ...names) { //Ordinary parameters with a rest parameter
console.log('Hello ' + name1); //Hello Cyrus
console.log('Hello ' + name2); //Hello Hash
return 'Hello ' + names.join(', Hello '
);
};
//calling the function with both discrete arguments and a array broken down by a spread operator
console.log(salute("Cyrus", "Hash", ...["James ", "Allen"])); //Hello James , Hello Allen
From this example, it's possible to use a combination of discrete parameters (name1, name2) and a rest parameter( …names) as well as a combination of discrete arguments(“Cyrus”,” Hash”) and a spread operator (...) that breaks down the array containing the rest of the function arguments (["James ", "Allen"]).
The first parameter(name1) corresponds to the first argument (“Cyrus”) while the second parameter (name2) corresponds to the second argument(Hash). the rest parameter corresponds to the rest of the arguments in the array - (["James ", "Allen"]).
If the number of discrete arguments are fewer than the number of discrete parameters as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
const salute = function (name1, name2, ...names) {//discrete parameters (name1,name2) & rest parameter(...names)
console.log('Hello ' + name1);//Hello Cyrus
console.log('Hello ' + name2);//Hello Hash
return 'Hello ' + names.join(', Hello ');
};
//discrete argument(Cyrus)
console.log(salute("Cyrus", ...["Hash", "James ", "Allen"])); //Hello James , Hello Allen
Then the second discrete parameter(name2) corresponds to the first argument (fist value of the array(Hash) which is broken down since the array is preceded by a spread operator(…).
Other uses of the spread operator;
- The spread operator is very useful when it comes to array manipulation. Examples;
// jshint esversion:9
"use strict";
/* jshint node: true */
// jshint esversion:9
"use strict";
/* jshint node: true */
const list1 = ["Hash", "Cyrus ", "Allen"];
const list2 = ["Node", "Codes ", "Poe"];
// Array manipulations
// Adding new items to an array
const list3 = [...list1, 'Blogs'];
console.log(list3); //[ 'Hash', 'Cyrus ', 'Allen', 'Blogs' ]
// creating new array from a combination of two arrays
const list4 = [...list1, ...list2];
console.log(list4);//[ 'Hash', 'Cyrus ', 'Allen', 'Node', 'Codes ', 'Poe' ]
// creating anew array from a combination of two arrays and new values
const list5 = [...list2, "Edgar", "George", ...list1];
console.log(list5);//['Node', 'Codes ', 'Poe', 'Edgar', 'George', 'Hash', 'Cyrus ', 'Allen']
- The spread operator is also useful when it comes to copying the contents of an immutable object and even adding new properties to it. To get the difference between mutable and immutable have a look at this article. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = {
name: "Cyrus",
Experience: "8 years",
profession: "Web developer"
};
//The original object
console.log(user); //{ name: 'Cyrus', profession: 'Web developer' }
//Edit the already provided values
console.log({ ...user, Experience: "5years" });//{ name: 'Cyrus', Experience: '5years', profession: 'Web developer' }
//Add new value to the object(gender) as an array
console.log({ ...user, profession: ["Web developer", "Blogger"], Gender: "Male" });//{name: 'Cyrus',Experience: '8 years',profession: ['Web developer', 'Blogger'],Gender: 'Male'}
The spread operator was introduced in javascript to replace the apply() function which is no longer in use but here is more info on it.
6. Default parameters;
New versions of javascript (es6 and later) allow the use of default parameters. This has its advantages including and not limited to ;
No need to pass in new values when the parameter value and the argument value are the same.
The possibility of adding new parameters without necessarily breaking the code.
The following example shows the use of default parameters in a function;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false) {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}`);
};
// calling the function without a change in married value
user("Cyrus", 31);//username: Cyrus, age: 31,Married: false
From the above function, the parameter married has a default value of false. So when calling the function, if the value to be passed as an argument is also false, then the argument is ignored when calling the function and the default parameter is automatically utilized.
On the other hand, if the marital status was true, the argument would have to carry a new value that overrides the default value as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false) {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}`);
};
// calling the function with a new value overrides the default value assigned to the parameter married(false)
user("Cyrus", 31, true);//username: Cyrus, age: 31, Married: true
With the introduction of default parameter values, it’s also possible to have multiple default values assigned to several parameters. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with the default values assigned to the multiple parameters
user("Cyrus", 31);//username: Cyrus, age: 31, Married: true
// Calling the function while passing new values to override the default parameter values
user("Cyrus", 31, true, "male");//username: Cyrus, age: 31, Married: true
-Passing an undefined value to default parameters;
This presents a very unique scenario. What if the first default parameter is to remain as is and carry the default value while the preceding default parameter values are to change.
In other languages, this proves quite an impossible task because when calling the function, the precedence of the arguments in correspondence with that of the parameters must be observed.
This means that the first argument (Cyrus) corresponds to the first parameter (firstName), the second argument (31) corresponds to the parameter( age ), and so on.
The scenario in question is where the default parameter( married )is to maintain its default value (false) which means there's no need to assign it a new argument when calling the function, but the last default parameter, (gender) requires a change in value.
This presents the impossible scenario which is skipping the overriding of the default( married) parameter to get to the default gender parameter that requires a new value of male during the function call.
The solution is passing an undefined value to the default married parameter (default parameter that has to maintain it’s default value) as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with the default values assigned to the multiple parameters
user("Cyrus", 31);//username: Cyrus, age: 31, Married: true
// Calling the function while passing undefined to keep the value of the first default parameter ( married) and passing a new value(male) to the second default parameter(Gender) to override the second default parameter's value.
user("Cyrus", 31, undefined, "male");//username: Cyrus, age: 31, Married: true
To summarize this, javascript follows the following rules while passing values to default parameters;
- If no new value is passed then the default parameter uses the assigned value. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with no new values to override the default parameter's values
user("Cyrus", 31);//username: Cyrus, age: 31, Married: false, Gender: Female
- If a good value is passed, then the default parameter adopts the new value. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with a new value for the first default parameter(married) overriding false with true
user("Cyrus", 31, true);//username: Cyrus, age: 31, Married: true, Gender: Female
- If a null is passed, then the value of the default parameter is changed to null. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with a null parameter that overrides the value of the second default parameter (gender) from female to null
user("Cyrus", 31, true, null);//username: Cyrus, age: 31, Married: true, Gender: null
- Finally, as already demonstrated, if an undefined is passed as the new value to a default parameter, the parameter retains its default value. This is very effective when there are preceding parameters that require to retain their default values from the one required to change its default value. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with an undefined value retains the default parameter value but allows changes to succeeding default parameters.
user("Cyrus", 31, undefined, "male");//username: Cyrus, age: 31, Married: false, Gender: male
This scenario also means that when no new values are provided as arguments to be passed to the default parameters, like this;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, married = false, gender = 'Female') {//default value for married is false
console.log(`username: ${firstName}, age: ${age}, Married: ${married}, Gender: ${gender}`);
};
// calling the function with no new values to override the default parameter's values
user("Cyrus", 31);//username: Cyrus, age: 31, Married: false, Gender: Female
- Javascript automatically and quietly assigns undefined as the new value to the default parameters which like already demonstrated, just makes the default parameters retain the already assigned default values.
7. Regular parameters vs Default parameters vs Rest parameter;
Normally in other programming languages, the use of default parameters is governed by a cardinal rule that states that;
- Regular parameters should not come after default parameters. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (married = false, firstName, age, gender = 'Female') {
//funcntion operations
};
This means that (married) - which is a default parameter should not appear before a regular parameter (firstName) - just like its a cardinal rule that;
- Rest parameter should never appear before a Regular parameter - Remember?
Javascript plays fast and loose with this rule because it actually allows it although, with the inclusion of linters as elaborated in this post, it may throw some warnings.
Although allowed in javascript, it's poor code design and practice where the code basically has undefined applied with no specific planning as seen in this example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (married = false, firstName, age, gender = 'Female') {//default value for married is false
console.log(`married: ${married}, userName: ${firstName}, Age: ${age}, Gender: ${gender}`);
};
user(undefined, "Cyrus", 31, undefined);//married: false, userName: Cyrus, Age: 31, Gender: Female
8. Using expressions as default parameter values;
javascript allows the use of an expression or expressions as default parameter values. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = 15000, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31);//UserName: Cyrus, Age: 31, Basic Salary:: 15000, Allowances: 6000
- Without passing new values to the default parameters, the expression (allowances = basicSalary * 0.4) is evaluated using the default values and the result is returned as a Allowances: 6000
- If a new value is passed for the basic salary then the new value overrides the default parameter value for basic salary(15000) and the new value is used to calculate the Allowances as follows ;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = 15000, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31, 5000);//UserName: Cyrus, Age: 31, Basic Salary:: 5000, Allowances: 2000
- If both the basic salary and the allowances are passed as new values, then both the default parameter values for the basic salary as well as the expression are ignored and the values passed are applied. This means that the defaults don’t have any effect as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = 15000, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31, 5000, 3000);//UserName: Cyrus, Age: 31, Basic Salary:: 5000, Allowances: 3000
-Caution when using expressions as default parameters;
It’s important to observe caution while using expressions as default parameters because they behave differently based on the arguments passed if any, to replace the default parameter values. Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = allowances * 10, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31, 5000, 3000);//UserName: Cyrus, Age: 31, Basic Salary:: 5000, Allowances: 3000
The above code already has one major problem where 'allowances' was used before it was declared, which is illegal for parameter variables. But this example still executes because the default values for the two default parameter expressions are unused since new values are passed ;(5000, 3000) for both parameters involved in the expression.
- Still if only one of the default parameters got a new value say, the basic salary, linting will throw a caution that 'allowances' was used before it was declared, which is illegal for 'param' variables but still javascript proceeds to evaluate the expressions as follows;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = allowances * 10, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31, 5000);//UserName: Cyrus, Age: 31, Basic Salary:: 5000, Allowances: 2000
- Finally, if no new values are passed to the default parameters, then javascript will throw a Reference error stating that allowances is not defined or something like;
ReferenceError: Cannot access 'allowances' before initialization.
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, age, basicSalary = allowances * 10, allowances = basicSalary * 0.4) {
console.log(`UserName: ${firstName}, Age: ${age}, Basic Salary:: ${basicSalary}, Allowances: ${allowances}`);
};
user("Cyrus", 31);//ReferenceError: Cannot access 'allowances' before initialization
-Default parameters vs Rest parameters;
Having looked at the default parameters in great detail as well as the Rest Parameter, its time to determine how useful is a combination of the two new and powerful javascript features;
- It’s imperative to remember that the default parameter may be omitted when calling a function so that it retains the default value and the rest parameter may receive zero or more values.
- Also as we have already covered, one of the cardinal rules of using the rest parameters is that it should always come last.
This presents the first reason why it's a bad idea to combine both default parameters and rest parameters -because the passing of default parameters cannot be left empty like it normally should, which forces the use of undefined to ensure the default parameters remain the same which as already covered, even though not erroneous, its the beginning of poor code design which makes the default parameter pretty unfit to use in this case. Example in code;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, basicSalary = 10000, ...skills) {
console.log(`UserName: ${firstName}, Basic Salary: ${basicSalary}, Skills: ${skills[0]}`);
};
//Forced use of undefined for the sake of the default parameter
user("Cyrus", undefined, ["Blogger", "Web developer", "Content creator"]);//UserName: Cyrus, Basic Salary: 10000, Skills: Blogger, Web developer,Content creator
- Note that it's illegal in javascript to try and assign a default parameter value to the rest parameter which basically means that the rest parameter does not support a default value, Example;
// jshint esversion:9
"use strict";
/* jshint node: true */
const user = function (firstName, ...skills = ["Blogger", "Web developer", "Content creator"]) {// illegal to do
console.log(`UserName: ${firstName}, Basic Salary: ${basicSalary}, Skills: ${skills[0]}`);
};
user("Cyrus");//SyntaxError: Rest parameter may not have a default initializer
Thank you for sticking with this very educative article on functions and parameters. I hope it was as educative to you as it was to me throughout the process of testing out and understanding the various examples used.
Take your time and handle one point after the other to grasp every point. Finally, like the article, follow me on Twitter for more in-depth articles just like this one and stay in touch.