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.
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.
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.
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.
arguments
Rest is like 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.
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.
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.