Spread Love: A Look into JavaScript's Rest and Spread Syntax

It all started with this tweet that simply read "{...love}." A nerdy little JavaScript play-on-words of some kind, but what does it mean?

First a little background

ECMAScript 2015 (aka ECMAScript 6th Edition or ES6 for short or just JavaScript) defines a new syntax (that is, ...) that, depending on context, can be used as rest or spread, which are basically opposites of each other.

Note that the rest/spread syntax should not be confused with the REST API for communicating with back-end servers. Same name; completely different technologies.

Also worth noting is that ... is a sequence of three . characters and not the Unicode U+2026 character.

The spread syntax

MDN web docs explains spread like this.

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

Think peanut butter. Instead of putting it on your toast in one big glob, you spread it across the bread evenly with a knife.

If that's as clear as mud peanut butter, then let's read on.

Spread is like Object.assign

The new spread syntax can be used in place of ES5's Object.assign. How so? Let's jump right in with an example.

Take the case where we have two objects, foo and bar.

const foo = { a: 1, b: 2 };
const bar = { b: 3, c: 4 };

We'd like to create a third object with keys from the other two. We know that this can be performed with Object.assign like this.

const fooBar = Object.assign({}, foo, bar);

The result is a new object with a value of {a: 1, b: 3, c: 4}.

It's easy enough to understand, but a little heavy. We call the static assign method on the Object class and pass in some arguments—two of which are self-explanatory—but what's with that empty object literal?

Psst ... It's there as a "destination" object. We could simply say Object.assign(foo, bar), but foo would be mutated.

Using the object spread syntax, we can accomplish the same thing but with less heft.

const fooBar = { ...foo, ...bar };

We are defining an object literal spread with the keys from foo, then with the keys from bar.

IMO, the newer syntax hides a lot of the complexities away. In both cases, the order of precedence is from left to right, meaning that bar will override any keys that are also in foo.

You should now possess the knowledge to get the joke, and know what it means to "spread love." Here's what I would consider the rest of the code joke.

const love = { isBlind: true, isLove: true, isABattlefield: true };
const anotherLove = { ...love };

The heart emoji isn't meant to be taken literally. It's just a cute way to visualize the pun.

Spread is like concat

We've seen how spread mimics Object.assign when dealing with objects. But what happens when we spread an array? Let's find out.

Say we have two arrays, and we'd like to create a third array with the contents of the first two.

const foo = [1, 2, 3];
const bar = [4, 5, 6];

Here's the traditional JavaScript way of accomplishing this using the Array concat method.

const fooBar = foo.concat(bar);

The resulting value of fooBar would be [1, 2, 3, 4, 5, 6].

Again, spread to the rescue.

const fooBar = [...foo, ...bar];

It looks a lot like our object-spreading example above, but this time we create an array. Each item from foo is spread into the array, then each item from bar. Again, I believe this new syntax to be more concise, compared to the old concat method.

Spread is like apply

In the examples above, we learned how spread can sometimes act like Object.assign and other times like concat. But here's yet another way to use the power of spreading—this time for function arguments.

Let's say you have an array of numbers and you'd like to find the maximum value.

const values = [4, 1, 6, 9, 2];
const max = findMax(values);

How could we go about writing findMax in the example above? There's the brute-force method where we loop through all values using reduce, returning the largest value.

findMax-good.js
function findMax(values) {
  return values.reduce(function (max, value) {
    return value > max ? value : max;
  }, -Infinity);
}

I love reduce, and that's some cool code, but probably not as efficient as it could be. It also adds a lot of visual clutter to the code.

The clever among you probably noticed that the code above is exactly what Math.max does. Here is an equivalent function, but without the looping.

findMax-better.js
function findMax(values) {
  return Math.max.apply(null, values);
}

Technically there's still looping involved, but it's done by the browser so it's 🔥 blazing fast!

That's neat, and a lot more terse, but ugly. You also might have to pause and ask yourself, "Is it call or is it apply? What's with that null context thing?"

Now watch what happens when we introduce argument spreading.

findMax-best.js
const findMax = (values) => Math.max(...values);

Spread is just syntactic sugar for something that we can already do using apply in ES5, but it greatly simplifies the code.

Note that the MDN documentation for Math.max states:

If no arguments are given, the result is -Infinity.

From the brute-force example above, now you see why (i.e., it's the seed or initial value).

Wait ... You can also spread strings?

Any iterable can be spread. And strings are iterable. So yes, you can spread strings. What would that look like? Take the following code.

const str = 'Hello World';
const arr = [...str];

Here we take a string "Hello World" and spread it into an array. The resulting value of arr would be as follows.

['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']`

So as you can see here, this is roughly the same as the following.

const arr = str.split('');

There is an important distinction, however. While split will separate between each UTF-16 codeunit, the spread syntax will separate between each Unicode character (i.e., codepoint).

The rest syntax

You can think of rest as the opposite of spread. It takes the "rest" of the things (parameters, object keys, array items) and assigns them to a variable.

Rest is like arguments

What do you suppose this does?

const foo = (...args) => args;

Here we have a standard ES6 JavaScript arrow function foo that accepts a variable number of arguments. It takes these arguments, creates an array, assigns it to the variable args. Then it returns that array.

If you call foo(1, 2, 3) it will return an array [1, 2, 3]. I'm not sure why you'd want to do that, but it's still crazy cool.

Now let's take a look at a more practical, real-world example.

const buildListString = (...words) => words.join(', ');

const listString = buildListString('red', 'white', 'blue');

Here we pass in a variable number of arguments to buildListString, and it returns a single string containing each argument separated by a comma.

For the example shown above, listString will contain the string "red, white, blue".

In the pre-ES6 days, you would be forced to do something like this.

buildListString.js
function buildListString() {
  return Array.prototype.slice.call(arguments).join(', ');
}

Yuck! If you're anything like me, you probably had to Google it each time you wanted to convert arguments into an array. When using rest, all of that is behind you.

Using rest when destructuring

Take the following example.

Foo.jsx
const Foo = ({ children, ...otherProps }) => (
  <div>
    <WrappedComponent {...otherProps} />
    {children}
  </div>
);

Here we define a React stateless functional component (SFC) named Foo and pass in props. The children prop is destructured into a variable called children, and the "rest" of the props are assigned to the variable otherProps.

If we looked at otherProps in the inspector, we would see that it is a normal JavaScript object containing keys for each prop passed to Foo. That is to say that there is nothing magical about what rest creates.

Conclusion

Rest and spread are powerful syntax additions to the language. You can use spread with objects and arrays, and when passing a variable number of arguments to a function.

Once you start using rest and spread, you'll never want to code without them.

So go out and {...❤️} for yourself.