Comments

JavaScript spread and rest

Have you ever seen three dots (…) in any modern JavaScript code? Do you know that this syntax is used for spread and rest, two different features of JavaScript? In this article, you’ll learn the multiple usages of both features and discover how they can make your code more succinct, readable and elegant.

One syntax, two features

Spread and rest are two different features that were included in EcmaScript 2015.

As we’re gonna see below, we could say that these features behave in opposite ways and, often, can be used as a complement to each other.

Now, let’s check what each one does.

Spread

Before explaining the spread operator, let’s check out an example where it could come in handy:

const config  = {
  height: 200,
  width: 400,
  backgroundColor: 'white',
  title: 'Just an ordinary window',
  content: 'something nice...'
};

showModalWindow(config);

function showModalWindow(config) {
  const windowConfig = {
    modal: true,
    showBackdrop: true,
    height: config.height,
    width: config.width,
    backgroundColor: config.backgroundColor,
    title: config.title,
    content: config.content
  };

  // The remaining logic is not relevant for the purpose of this article
}

The above piece of code contains a function that shows a modal window. This function receives a configuration object with the available options to render this modal. It also composes a new configuration object that could, hypothetically, be passed to a more generic function that knows how to show different kinds of windows.

As you may have noticed, in order to copy the values from the config object supplied as an argument to the new object that’s being composed inside the function, each option was explicitly assigned. This is too verbose and could be even worse if we had more options.

There are different ways of making this code more succinct:

  • Iterating over the config object properties in order to copy each option from one object to the other.
  • Using Object.assign
  • The most concise, idiomatic and – IMO – elegant option: using the spread operator

Using the spread operator with objects

Now, look at this new version of the function from our previous example:

function showModalWindow(config) {
  const windowConfig = {
    modal: true,
    showBackdrop: true,
    ...config
  };

  // The remaining logic is not relevant for the purpose of this article
}

After this change, our function will spread the own properties of the config parameter into the windowConfig object. This version is flexible enough to deal with any possible combination of options used.

One important point of attention here is that occasionally it might be important to implement some filtering logic on objects that will be spread, in case only a restricted set of properties should be allowed.

const config  = {
  height: 200,
  width: 400,
  backgroundColor: 'white',
  title: 'Just an ordinary window',
  unwantedConfig: 'this should not be allowed to pass'
};

showModalWindow(config);

function showModalWindow(config) {
  const filteredConfig = // implement some filtering logic here...

  const windowConfig = {
    modal: true,
    showBackdrop: true,
    ...filteredConfig
  };
}

Using the spread operator with arrays

You can use the spread operator with arrays as well. In fact, when first released in ES2015, spread only worked with arrays (the support for objects was introduced in ES2018).

The usage is equally simple and self-explanatory:

const n1 = [99, 88, 77];
const n3 = [33, 22, 11];
const n2 = [...n1, 66, 55, 44, ...n3];

console.log(n2); //=> [99, 88, 77, 66, 55, 44, 33, 22, 11]

The items of the n1 and n3 arrays are spread at the beginning and end of n2, respectively. These values are combined with others – 66, 55 and 44 – that we explicitly declared when initializing this array.

Here’s another example, combining three arrays into a new one:

const signUpStatuses = ['SIGNUP_BASIC_INFO', 'SIGNUP_PAYMENT_INFO', 'SIGNUP_CONTACT_INFO'];
const analysisStatuses = ['PENDING_ANALYSIS', 'REJECTED', 'PENDING_DOCUMENTATION'];
const postApprovalStatuses = ['ACTIVE', 'SUSPENDED', 'BLACKLIST'];
const statuses = [...signUpStatuses, ...analysisStatuses, ...postApprovalStatuses];

As a curiosity, it’s even possible to spread an array into an object. By doing so, the array items will be the values and their indexes become the keys:

const monthsArr = ['January', 'February', 'March', 'April'];
const monthsObj = { ...monthsArr };

console.log(monthsObj); //=> { 0: "January", 1: "February", 2: "March", 3: "April" }

Even though it’s a bit hard to find a real-life usage for this kind of “trick” (I myself have never used it), I like to know that it’s possible.

Using the spread operator in function calls

Another way of using the spread operator is on function calls. It can spread the items of an array in a sequence of arguments when calling a function. Let’s check out some simple examples to illustrate this feature:

const ids = [11, 12, 13, 14, 15];
retrieveItemsByIds(...ids);

The above example invokes a function and passes the 5 numbers from the ids array as arguments. It is equivalent to:

retrieveItemsByIds(11, 12, 13, 14, 15);

If you want to play a bit more, you could open your browser’s console and spread this array into console.log:

console.log(...ids);

Now, take a look at this example:

const pixels = [
  [10, 25, '#DDD'],
  [11, 26, '#CCC'],
  [12, 27, '#BBB'],
  [13, 28, '#AAA'],
  [14, 29, '#999'],
];

pixels.forEach(pixel => drawPixel(...pixel));

function drawPixel(x, y, color) {
  // pixel drawing logic comes here
}

This time, we’re creating a bidimensional array; then, we iterate over it to get each sub-array at a time; finally, we call the drawPixel function on every iteration and spread the items of our sub-arrays as arguments for this call.

Note that, in order to have the arguments correctly supplied, the sub-arrays values adhere to the drawPixel function’s signature order (x coordinate, y coordinate and color).

Rest

We can safely say that even sharing the same syntax, spread and rest do opposite things. As we saw, you can use the spread operator to break up objects and arrays, spreading them into other objects, arrays and function calls. Rest, in turn, groups array items into a new array, or object properties into a new object.

Using rest in destructuring assignment

Even though explaining destructuring assignment is not the focus of this article, here’s a very simple snippet to exemplify how it works:

// Destructuring assignment for arrays
const ipOctets = [192, 168, 0, 1];
const [octet1, octet2, octet3, octet4] = ipOctets;

console.log('First octet:', octet1); //=> First octet: 192
console.log('Second octet:', octet2); //=> Second octet: 168
console.log('Third octet:', octet3); //=> Third octet: 0
console.log('Fourth octet:', octet4); //=> Fourth octet: 1

// Destructuring assignment for objects
const person = { firstName: 'John', lastName: 'Doe', age: 42 };
const { firstName, lastName, age } = person;

console.log('First name: ', firstName); //=> John
console.log('Last name: ', lastName); //=> Doe
console.log('Age: ', age); //=> 42

In a nutshell, this feature allows you to assign the items of an array or the properties of an object to individual variables.

If you’re not familiar with the concept of destructuring, I recommend you to read this article.

We can use rest with destructuring assignment either for objects or arrays. Let’s check out an example with objects first:

const user = {
  name: 'Frederick Charles Krueger',
  motherName: 'Amanda Krueger',
  age: 80,
  address: '1428 Elm Street'
};

const { address, ...personalInfo } = user;

console.log('Address:', address); //=> 1428 Elm Street
console.log(personalInfo);

/*
=> {
  age: 80,
  motherName: "Amanda Krueger",
  name: "Frederick Charles Krueger"
}
*/

In the above example, we extract one property of the user object to a variable (address) and, by using rest, we group the remaining properties into a new object (personalInfo), that doesn’t contain the address property.

Now, look at this example with arrays:

const ranking = ['ALC', 'QUE', 'JNS', 'BMX', 'TAK', 'SHI', 'BAT', 'CFO', 'ACE', 'CAT'];
const [first, second, third, ...others] = ranking;

console.log('1st place:', first); //=> "1st place: ALC"
console.log('2nd place:', second); //=> "2nd place: QUE"
console.log('3rd place:', third); //=> "3rd place: JNS"
console.log('Honorable mentions:', others.join(', ')); //=> "Honorable mentions: BMX, TAK, SHI, BAT, CFO, ACE, CAT"

The logic here is the same. We explicitly assigned the first three items of the ranking array to separate variables, grouping the remaining ones into the others array.

Maybe you have noticed, but it’s important to stress that the rest syntax can only be used at the end of a destructuring assignment. Hence, this example will result in an error:

const codes = [11, 22, 33, 44, 55];
const [first, ...others, last] = codes;

Using rest in a function signature

You can specify multiple variables as the first parameters in a function signature and use rest to group the remaining ones into a single array. Under the hood, it behaves exactly as destructuring for arrays:

function printRanking(first, second, third, ...others) {
  console.log('1st place:', first);
  console.log('2nd place:', second);
  console.log('3rd place:', third);
  console.log('Honorable mentions:', others.join(', '));
}

printRanking('ALC', 'QUE', 'JNS', 'BMX', 'TAK', 'SHI', 'BAT', 'CFO', 'ACE', 'CAT');

/*
=> "1st place: ALC"
=> "2nd place: QUE"
=> "3rd place: JNS"
=> "Honorable mentions: BMX, TAK, SHI, BAT, CFO, ACE, CAT"
*/

And in case you have your arguments in an array, you could even use spread in your function call:

const ranking = ['ALC', 'QUE', 'JNS', 'BMX', 'TAK', 'SHI', 'BAT', 'CFO', 'ACE', 'CAT'];
printRanking(...ranking);

This way, you can use spread and rest as a complement to each other.

Another possibility is to group all passed arguments:

retrieveItems(111, 222, 333, 444, 555);

function retrieveItems(...ids) {}

The above approach is better than using the arguments object, since arguments is not a real array.

Conclusion

  • The … syntax in JavaScript is used for two features which were introduced in ES2015: spread and rest.
  • The spread operator copies the properties from an object to another object. It also copies the items from an array into another array or even object (using the original indexes as keys). Last, but not least, it can spread the items from an array into a function call, using each item sequentially as arguments.
  • The destructuring assignment allows you to assign the values of properties from an object to separate variables in a very succinct syntax. It also works with arrays, making it possible to extract values from a given array sequentially into separate variables, all by using a one-liner syntax.
  • You can use the rest syntax to group function parameters into an array. By using rest, it’s possible to declare some normal parameters at the beginning of a function’s signature and collect the remaining passed arguments into an array variable. It’s even possible to group all of the arguments passed to a function and manipulate them inside the function as a single array. You can combine rest with destructuring assignment as well.

Do you want to become a JavaScript master? If so, you need to check out Mosh’s JavaScript course. It’s the best JavaScript course out there! And if you liked this article, share it with others as well!

JavaScript hacker, front-end engineer and F/OSS lover.
Tags: , ,

Leave a Reply

Connect with Me
  • Categories
  • Popular Posts