Spread Operator (...), Rest Parameters and Destructuring

javascriptdestructuringspread operatorrest parameters
Table of contents
  • Spread Operator (...)
  • Rest Parameters

Spread Operator (...)

The apply() method of function object is a convenient tool for passing an array as arguments to a function. For example, it’s commonly used with the Math.max() method to find the highest value in an array. Consider this code fragment:

js
const myArray = [5, 10, 50];
Math.max(myArray); // Error: NaN
Math.max.apply(Math, myArray); // 50

The Math.max() method doesn’t support arrays; it accepts only numbers. When an array is passed to the Math.max() function, it throws an error. But when the apply() method is used, the array is sent as individual numbers, so the Math.max() method can handle it.

With the introduction of the spread operator, we no longer need to use the apply() method. With the spread operator, we can easily expand an expression into multiple arguments:

js
const myArray = [5, 10, 50];
Math.max(...myArray); // 50

Here, the spread operator expands myArray to create individual values for the function.

Rest Parameters

The rest parameter has the same syntax as the spread operator, but instead of expanding an array into parameters, it collects parameters and turns them into an array. If there are no arguments, the rest parameter will be set to an empty array.

js
function myFunction(...options) {
return options;
}
myFunction('a', 'b', 'c'); // ["a", "b", "c"]

A rest parameter is particularly useful when creating a variadic function (a function that accepts a variable number of arguments). The rest operaroe must be the last argument; otherwise, a syntax error will occur. Another limitation is that only one rest parameter is allowed in the function declaration

js
function checkSubstrings(string, ...keys) {
for (let key of keys) {
if (string.indexOf(key) === -1) {
return false;
}
}
return true;
}
checkSubstrings('this is a string', 'is', 'this'); // true

Destructuring

Destructuring is a new feature that enables us to extract values from arrays and object and to assign them to variables using a syntax that is similar to object and array literals. The syntax is clear and easy to understand and is particularly useful when passing arguments to a function.

A configuration object is often used to handle a large number of optional parameters, especially when the order of properties does not matter. Consider this function:

js
function initiateTransfer(options) {
let protocol = options.protocol,
port = options.port,
delay = options.delay,
retries = options.retries,
timeout = options.timeout,
log = options.log;
// ...
// code to initiate transfer
}
const options = {
protocol: 'http',
port: 800,
delay: 150,
retries: 10,
timeout: 500,
log: true
};
initiateTransfer(options);

This pattern is commonly used by JavaScript developers, and it works well, but we have to look inside the function body to see what parameters it expects. With destructured parameters, we can clearly indicate the parameters in the function declaration:

js
function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
// ...
// code to initiate transfer
};
const options = {
protocol: 'http',
port: 800,
delay: 150,
retries: 10,
timeout: 500,
log: true
}
initiateTransfer(options);

In this function, we’ve used an object destructuring pattern, instead of a configuration object. This makes our function not only more concise, but easier to read. We can also combine destructured parameters with regular ones. Note that a type error will be thrown if parameters are omitted in the function call:

js
function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
// code to initiate transfer
}
initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'

This is the desired behavior when we need parameters to be required, but what if we want them to be optional? To prevent this error when parameters are missing, we need to assign a default value to destructured parameters:

js
function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) {
// code to initiate transfer
}
initiateTransfer(); // no error

We can also assign a default value to each destructured parameter:

js
function initiateTransfer({
protocol = 'http',
port = 800,
delay = 150,
retries = 10,
timeout = 500,
log = true }) {
// code to initiate transfer
}

In this example, every property has a default parameter, eliminating the need for us to manually check for undefined parameters and assign default values inside the function body.

In JavaScript, everything is passed by value, but when we pass a variable that refers to an object (including arrays), the “value” is a reference to the object, and changing a property of an object referenced by a variable does change the underlying object.

The art of knowing is knowing what to ignore - Rumi