JavaScript Shinies: Rest and Spread
By: Matt Burgess
Tags: JavaScript

JavaScript Shinies: Rest and Spread

An ongoing look at JavaScript features that are newly here or on the way. The Rest and Spread syntax uses three dots to do a surprisingly broad number of things.

Once again, Rest and Spread are a pair of concepts that do something that isn’t specifically unique to them, but they make it easier.

It’s easier to take them one at a time and explain it. In both cases, the syntax is the same: …stuff . Medium is helpfully squishing that down to an ellipsis but whatever, it’s three dots, periods or full-stops, whatever you like to call them.

The Spread Syntax — Arrays

The concept of spread is essentially that it is the “contents of x” where x is some sort of iterable like an array. It’s probably better to show a code example to give us something to work with.

function makeFullName(firstName, lastName) {

return `${firstName} ${lastName}`;

} let user = ['Sam', 'Stone']; makeFullName(...user);

In this example, the user array is going to get broken apart and passed into the function. It’s important to note the difference between the array and the contents of the array, the latter of which we are dealing with here.

let exampleArray = ['cat', 'fish', 'dog']; let newArray1 = [exampleArray, 'mouse', 'horse'];

let newArray2 = [...exampleArray, 'mouse', 'horse'];

Though seemingly similar, the first example will have a nested array, while the second will have a single array of all the elements.

Because spread essentially “pulls out” the contents of an array it is its own separate entity. To clarify:

let exampleArray = ['cat', 'fish', 'dog']; let newArray = [...exampleArray]; exampleArray.push('ant')

While the exampleArray will have a new member, the newArray will not. The usefulness of this is difficult to see at a glance, but more obvious in objects, which we might as well jump to now.

The Spread Syntax — Objects

Where this really excels is handling objects, in particular manipulating and creating them. In much the same way as arrays get their elements sprayed out, ...object pulls out the properties of the object in question.

This makes it especially easy to do things like copying an object or creating one.

let cat = { name: 'Fluffy', breed: 'Exotic Shorthair', age: 5 };

let otherCat = { ...cat };

The otherCat object will be a direct copy of all the properties of our first cat. Note that this is very different from let otherCat = cat as that would just be a pointer back to the original cat.

Where it gets more interesting is when you start combining things.

let firstCatDetails = { name: 'Fluffy', breed: 'Exotic Shorthair', age: 5 };

let secondCatDetails = { colour: 'Black and white', desexed: true, name: 'Wiggles', age: 3 }; let otherCat = { ...firstCatDetails, ...secondCatDetails };

An object was created with Fluffy’s details first. The properties of Wiggles were then written over that object.

One place where this is commonly used is in things like setting state in React. You can create a new object easily from React’s current state (or a property of it) and then modify that copy, then reassign the whole thing. This keeps to React’s preferences for immutable state data. Redux, in particular, often does this.

What’s also kind of neat is that the object doesn’t have to be an object. If a function call returns and object you can spread the whole thing.

const getUserById(id) => UserModel.find({id}); const allUserData = {...userInfo(7), transactions: userHistory}

This will have whatever the UserModel from Mongoose (presumably) contains, as well as a new key for transactions with whatever the userHistory array contains.

To give another example, in a previous article we used array.reduce() to squish together some data, reformatting an array into an object.

let people = [

{id: 1, name: "Ash", age: 10},

{id: 2, name: "Sam", age: 23},

{id: 3, name: "Shannon", age: 46},

{id: 4, name: "Kim", age: 18}

]; const personReducer = (peopleObj, person) => {

peopleObj[person.id] = person.name;

return peopleObj;

}; const peopleById = people.reduce(personReducer, {}); // {1: "Ash", 2: "Sam", 3: "Shannon", 4: "Kim"}

This is fine as it is, but we can use a spread to simplify the reduce callback a little bit.

const personReducer = (peopleObj, {id, name}) => {

return {...peopleObj, [id]: name};

};

Of course, we could simplify this at least syntactically.

const personReducer = (peopleObj, {id, name}) => ({...peopleObj, [id]: name});

Not relevant to spread at all, but some part of the above is worth noting.

const keys = [ "firstName", "secondName" ]; const first = {keys[0]: 'Taylor'};

const second = {[keys[1]]: 'Jones'};

While the first one is invalid and will give you an error of unexpected token: [ the second one is entirely correct and does what you would expect, creating an object with a property of secondName that has a value of Jones .

Rest Syntax

The other place this construct gets used regularly is as one of the arguments of a function.

function addNumbers(...nums) {

return nums.reduce((total, newValue) => total + newValue);

} addNumbers(1, 2, 3, 4) // 10

addNumbers(1, 100) // 101

This function will work no matter how many arguments are passed in. It becomes variadic, a fancy word for a function with an unlimited arity. And arity being a fancy word for the number of arguments a function takes. And a function is… nevermind.

All of the arguments are going to be an array. In fact something similar to this could be achieved by using arguments , but there are a number of limitations with that. For a start, something like this is nearly impossible.

function peopleWithPets(ownerName, ...petNames)

That will give an array of pet names while still keeping a fixed owner name, something not easily attained with other solutions.

Naturally, these different bits of functionality go very well together.

function addNumbers(...nums) {

return nums.reduce((total, newValue) => total + newValue);

} const myNumbers = [4, 6, 9, 1, 28];

addNumbers(...myNumbers) // 48

This (or more precisely the opposite) is a very effective way of taking all the inputs of one function, and sending them to another.

const callStateFunction(action, ...values) => {

return state[action](...values);

} callStateFunction('setUser', userObject);

callStateFunction('replace', 'steve', 'susan');

callStateFunction('reset');

If it seems weird that I’m changing function definition styles all over the place the reason is that back off you’re not my dad.

The above looks very arbitrary, but will be relevant to future discussions. Again, while this looks a bit confusing it’s very common in more abstract systems used by frameworks, state management libraries, and more.

We’ll actually go more into this in a future article. Suffice it so say (for now) that this pattern is actually pretty common.

Class Whatever { constructor(dataService) {

this.data = dataService;

} someFunction(...arguments) {

this.data.someOtherFunction(...arguments);

}

}

This just unpacks and repacks the arguments into a new function. Which doesn’t sound as useful as it actually is.

Enabling This Feature

Ok, this is where it starts to get more complex than previous examples. Rest/Spread is not enabled by default in all browsers. It’s a stage 4 proposal, but not fully finalised. Stage 4 is pretty late, though, and should be pretty well supported. Everything demonstrated above will work in all browsers except Edge. Because any browser Microsoft has made for the last 20 years is hot garbage without exception.

In truth most of the features above will work in Edge as well, the exception is using spread in object literals. The example we used above was this.

let cat = { name: 'Fluffy', breed: 'Exotic Shorthair', age: 5 };

let otherCat = { ...cat };

Which is honestly ones of the more useful parts. You can enable this feature by installing the Babel Object Rest Spread package in whatever you’re using to build.

If you’re using this functionality extensively, make sure you test carefully in Edge browser if you care to support it. I won’t tell anyone if you don’t.