How to Use the Spread Operator (...) in JavaScript
Juan Cruz Martinez
Posted on May 30, 2020
ES6 has introduced many new features into JavaScript, among them the spread operator (...)
, which expands an iterable object into a list of its individual elements.
If it’s not clear yet, don’t worry, we will go into details in the next sections when we actually learn to use it with real-life scenarios.
Copy arrays or objects
Look at the following script, can you tell what’s the output?
const listA = [1, 2, 3]
const listB = listA
listB.push(4)
console.log('listA:', listA)
console.log('listB:', listB)
The output for that example is the following:
"listA:" [1, 2, 3, 4]
"listB:" [1, 2, 3, 4]
Wait! what? Why did listA
changed its value when we clearly only changed listB
. Well, the reason is simple, when we did the assignment:
const listB = listA
Javascript assigned to listB
a reference to listA
, so in essence listA
and listB
are pointing to the same list in memory.
So then, how do I create a copy? Here is where spread operators enter the picture. Let’s look again at the same example using spread operators:
const listC = [1, 2, 3]
const listD = [...listC]
listD.push(4)
console.log('listC:', listC)
console.log('listD:', listD)
And the output:
"listC:" [1, 2, 3]
"listD:" [1, 2, 3, 4]
In this case, by using the spread operator we are making a new copy in memory of the array, so when we update listD
we are not affecting by any means listC
.
See it yourself in action:
Similarly, we can use this technique to copy objects, however, there’s a catch:
const article = {
title: 'How to Use the Spread Operator (...) in JavaScript',
claps: 1000000,
author: {
name: 'Juan',
publication: 'LiveCodeStream'
}
}
const articleCopy = { ...article }
articleCopy.title = 'Strange behaviours with spread operator and deep copy';
articleCopy.author.name = 'JC';
console.log('Original title:', article.title);
console.log('Original author:', article.author.name)
console.log('Copy title:', articleCopy.title)
console.log('Copy author:', articleCopy.author.name)
Before we explain what is happening here, let’s look at the output:
Original title: How to Use the Spread Operator (...) in JavaScript
Original author: JC
Copy title: Strange behaviours with spread operator and deep copy
Copy author: JC
Again, what?! Now that we have used the spread operator we got a copy in memory of the original object, however, some properties copied in values and some as a reference, as the case of the author (note how the title changed only for the copy but the author
was altered by both the original and the copy).
What happened here is that the spread operator won’t do a deep copy, but it would take each of the elements in the original object/list and would map them to a new position in memory. However, if one of the elements happened to be a reference to another object, it will simply make a copy of the reference into memory, but it won’t change what it’s referenced to.
There are ways to make a deep copy using the spread operator, but we won’t look at them in this post, however, if you are curious but lazy to google it, this article from Dr. Derek Austin explains it well in detail.
Codepen:
Merging arrays or objects
The spread operator is very useful to copy objects, but we can also use it to merge multiple objects or lists into a single object.
Let’s look at an example of merging lists and one merging objects:
const list1 = [1, 2, 3]
const list2 = [4, 5]
const mergedList = [...list1, ...list2]
console.log('Merged List: ', mergedList)
const obj1 = {a: 1, b: 2}
const obj2 = {c: 3}
const mergedObj = {...obj1, ...obj2}
console.log('Merged Object: ', mergedObj)
This works as expected, following the considerations from above. Here is the output:
Merged List: [1,2,3,4,5]
Merged Object: {"a":1,"b":2,"c":3}
However, things can get a bit strange with JavaScript:
const weird1 = {...obj1, ...list2}
console.log('Merged list as object', weird1)
In this case, we merge our obj1
and list2
into an object, do you know the result?
Merged list as object {"0":4,"1":5,"a":1,"b":2}
It surprisingly worked! It looks a bit strange, but you can easily predict what the result would be.
Would it also work the other way around? Merging an object into a list?
const weird2 = [...obj1, ...list1]
console.log('Merged list as object', weird2)
Any guess?
object is not iterable (cannot read property Symbol(Symbol.iterator))
Maybe not what you expected, but it’s not possible to merge an object, or for that case, to copy an object into a list. The reason is that you cannot iterate over an object. If you implement the object as an iterable then it would be possible to do it.
Codepen:
Passing arguments
Have you ever tried to find the maximum (or minimum) value on an array? Your first answer to this question is probably to use the Math.max
function, however, it won’t work, we need to do something else. Why?
Math.max
as other similar functions, by definition expect multiple parameters, look at the definition on MDN. If we try to pass an array as one value, that value won’t be a number like expected, and the function will return NaN
. To fix this we can use a spread operator, to convert the array to a list of arguments as follows:
console.log('Math.max(1, 2, 3)', Math.max(1, 2, 3))
console.log('Math.max([1, 2, 3])', Math.max([1, 2, 3]))
console.log('Math.max(...[1, 2, 3])', Math.max(...[1, 2, 3]))
And the output:
Math.max(1, 2, 3) 3
Math.max([1, 2, 3]) null
Math.max(...[1, 2, 3]) 3
Nice! But how can I create a function like that myself? let’s look at an example:
function test(param1, ...args) {
console.log(' -> param1', param1)
console.log(' -> args', args)
}
console.log('test(1):')
test(1)
console.log('test(1, "a", "b", "c"):')
test(1, 'a', 'b', 'c')
And the output:
test(1):
-> param1 1
-> args []
test(1, "a", "b", "c"):
-> param1 1
-> args ["a","b","c"]
Using the spread as part of the function declaration is a great way to convert the arguments into an array. More on this topic next.
Codepen:
Destructing arrays or objects
We saw how to copy and merge objects, but…. can we “unmerge” objects? It’s called destructing… and yes! let’s take a look:
console.log('first', first)
console.log('second', second)
console.log('rest', rest)
output:
first 1
second 2
rest [3,4,5]
And similarly, we can do the same with objects:
const article = {
title: 'Cool article',
claps: 10000000,
author: {
name: 'Juan'
}
}
const { title, claps, author: { name }} = article
console.log('title', title)
console.log('claps', claps)
console.log('author name', name)
Output:
title Cool article
claps 10000000
author name Juan
With objects something interesting happens, we can even retrieve nested properties. This technique is widely used in React components and when using Redux or similar.
Now that we understand this feature, you probably have a better idea on what happened exactly in the previous example of the function declaration.
Codepen:
NodeList to Array
Simply as it sounds we can do something like:
[...document.querySelectorAll('div')]
Convert string to characters
A string is an iterable, and we can use them to expand them as a list of characters as follows:
const name = 'Juan'
const chars = [...name];
Remove duplicates
Can we use spread operators to get only unique values? Yes, but not directly… we would need to make use of something else in conjunction with the spread operator, Sets.
const list = [1, 3, 1, 3, 3, 2]
const uniqueList = [...new Set(list)]
The value for uniqueList
is now:
[1, 3, 2]
Conclusion
The spread operator (…) is very useful for working with arrays and objects in Javascript. You will see them a lot when working with frameworks such as React, and when developing reducers.It’s definitely an important feature to learn and master if you work with Javascript.
If you want to learn more about ES6, check my post: When Not to Use Javascript Arrow Functions
Thanks for reading!
Posted on May 30, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.