DEV Community

Marina Mosti
Marina Mosti

Posted on • Edited on

Understanding the Spread Operator in JavaScript

{...object}

The first time I looked at this I was really confused about what it was supposed to mean, or even do, but harnessing the power of the spread operator in JavaScript has definitely payed off.

In my latest entry to Hands-on Vue for Beginners series, I used a spread operator in the tutorial and promised I would make a post explaining what it is, and how it works, for anyone that is not yet familiar with it.

Disclaimer. The spread operator is an ES6 feature, and thus is not supported in every browser (IE strikes again). However you can rest assured that it will work on all major and modern browsers. Browser Compatibility

Diving in

MDN explains it 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.

And I don't know about you, but for me when I was trying to learn all this it was super daunting and confusing. So let's break it down to digestible pieces.

Imagine you have a function, where you want to add three values:

function add(a, b ,c) {
  return a + b + c;
}

Now imagine, you have an array of values:

const values = [1, 2, 3];

Now, if we ask ourselves, how would we go about putting those values into the function add them. A practical approach would be to maybe do something like this:

const sum = add(values[0], values[1], values[3]);

I mean, it sort-of works. Right? But take it up a nudge, what happens when you have a function that will accept a dynamic number of arguments? Or even simpler yet, what if we have a scenario where this function wants 20 values and I give you an array with 20 numbers.

This is when things start to get really ridiculous, are you going to access all the values directly via index? (There's ways to do this with array functions like reduce, I know, but bare with me for the sake of example.)

Consider the following example:

function sum(a,b,c,d,e,f,g,h,i,j,k,omgwhy) {
 return a+b+c+d+e+f+g+h+i+j+k+omgwhy;
}

const values = [1,2,3,4,5,6,7,8,9,11,12];

Here's where the spread operator shines! Imagine that this values array is a box, it contains all these numbers in it in a VERY neat and orderly fashion, each one has an index - and that contains it perfectly.

When we access an array's index value like this values[1] we are very neatly taking out that single value, which is what we did on the first example. But to continue with the metaphor, what if we wanted to take out ALL the values in the exact order in which they are in the box?

Maybe it could be cool to be able to do something like this values[0-11], but instead we type it like this ...values.

Now in this case in particular with the function, you would say:

const result = sum(...values);

Tada! Now, watch carefully because when take the values out of the array they're not just getting thrown around into the wind, they're all going into their respective parameters in the function. a will get 1, b will get 2 and so on.

Merging

Alright, so spreading arrays into functions is nice. But i've honestly not used it a lot. However merging two arrays together or two objects becomes a breeze!

Let's cook up an example:

const favoriteGames = ['Zelda', 'Metroid'];
const favoriteBands = ['Queen', 'RHCP'];

const lifeFavorites = [...favoriteGames, ...favoriteBands];

So first we're just defining a couple of arrays for examples, favoriteGames and favoriteBands. But let's take a look closely at the third line.

const lifeFavorites = we're going to store the result of the merge here, nothing too fancy and we're setting it to be equal to a NEW array.

Notice = [somestuffshere];

This is super important to understand, when you spread an array or an object those spread values needs to be used somehow, you need to clean up after yourself. So in this case we're saying:

Ok, JS, grab the contents of these two array boxes, dump all the crap they have in them into this new box and call it lifeFavorites. Thankfully the actual crap-dumping is done in a very orderly fashion and order of the values will be respected.

So the result will actually be:

['Zelda', 'Metroid', 'Queen', 'RHCP']

Making copies

Another great way to use the spread operator is to make a copy of an array or an object. In JavaScript when you assign an object or array to a variable you're actually storing something called the "pointer", which sort of works like the address of where that object is being stored.

So if you do:

const obj = {name: 'Marina'};
const anotherObj = obj;

The big problem here will be that both obj and anotherObj are actually pointing to the same object. So if you change one or the other, they will both change because the pesky pointer is addressed to the same object.

In frameworks like Vue this becomes a real problem and a real pain to debug, because in some cases like when making copies of props you never want to modify the original object. In React this is also used when changing the state, you're not supposed to directly modify the value.

So how do we make a copy with the spread operator?

const baseObject = {name: 'Marina'};
const myCopy = {...baseObject};

const baseArray = [1, 2, 3];
const arrayCopy = [...baseArray];

Something important to note is that when making a copy of any object/array using a the spread operator the copy will be shallow. This means that if your object has a child object, that child will still have a pointer to the original object. (Thanks to everyone that pointed this in the comments)

const original = { name: 'Parent', child: { name: 'Child' } };
const copy = {...original};

// The thing to note here is that the COPY will have a child property, but this
// child property is actually pointing to the same exact object as ORIGINAL

// So:
// original.child === copy.child

That's it! Once again, we're pouring out our boxes into another box that contains it, and that will ensure that the resulting object/array are actually a copy of the original - and you can safely modify them without fear of messing with the first.

Top comments (21)

Collapse
 
sleepyfran profile image
Fran González • Edited

Good article! However, I feel like it's important to mention that the spread operator does not perform a deep clone on objects, but rather a shallow copy. This means that the spread operator will copy simple values (such as integers, strings and such) but keep the same references for objects. For example:

const obj = { a: 1, b: { a: 1 } };
const copy = {...obj}
copy.b.a = 2

console.log(obj.b.a)
console.log(copy.b.a)

This prints 2 and 2 because the property b was shallow copied, which makes copy.b point to the exact same object as obj.b.

Collapse
 
marinamosti profile image
Marina Mosti

Hey Fran, thanks for the addendum. I actually should have probably put a little more clarity on this part, but i've made a small correction and your example is also super clear

Collapse
 
qm3ster profile image
Mihail Malo • Edited
const randoms = function*(x){while(true)yield Math.random()*x|0}
const [a, b, c] = randoms (1000)
console.log(a, b, c)

open eye crying laughing emoji

Collapse
 
jxnday profile image
Jackson Day • Edited

Great intro to spread! Looks super useful for making copies. Though at the end, I believe you mean to say

original.child == copy.child

(not

original.child == original.copy

) is that right?

Collapse
 
marinamosti profile image
Marina Mosti

🤦‍♀️This is why you dont hotfix posts while doing 3 other things. Thanks! :)

Collapse
 
rher profile image
Raul Hernandez

Hi Marina,
thanks a lot for your instructive and funny to read article. Like your style.

I would like to ask you (if you ever revisit this article) , about what you say

both obj and anotherObj are actually pointing to the same object. So if you change one or the other, they will both change because the pesky pointer

How the object should be changed in order to change also its copy?
I just tried in the console to do

baseObject.name="otherName
And when I consolelog both, the copy remain untouched.

Thanks in advance if you ever take the time to answer!

Collapse
 
devdammak profile image
Damola Adekoya

I love spread operator it has saved me lot of times.. instead of using Array.prototype.slice..
Because I always mix it up with Array.prototype.splice...

Collapse
 
marinamosti profile image
Marina Mosti

All ways lead to Rome :) But this one is definitely one of the easier/shorter ones

Collapse
 
devdammak profile image
Damola Adekoya

Yeah

Collapse
 
csabdulwahab profile image
Abdul Wahab

Once again thankyou

I have read so many articles on the same topics
But you are special. I know i should be writing the code as well at the same time
But here i go looking to read another article from you

Collapse
 
ipas profile image
iPAS

Does it seems like in Python? that

func(1, 2, a=1, b=2)

def func(args, **kwargs):
print(*args) --> [1, 2]
print(
*kwargs) --> {a: 1, b: 2}

Collapse
 
drozerah profile image
Drozerah

Really nice and easy way to make copy ! :)

Collapse
 
marinamosti profile image
Marina Mosti

Thanks Drozerah!

Collapse
 
eidsonator profile image
Todd Eidson

I always preferred the term 'splat' operator. It sounds so much cooler. Nice article, thanks!

Collapse
 
marinamosti profile image
Marina Mosti

Thanks for reading :)

Collapse
 
wtaylor45 profile image
Will Taylor

Really great explanation of this! Especially like that you gave the example of copying an object in that way. That's a super important use case.

Collapse
 
marinamosti profile image
Marina Mosti • Edited

You're right, it's very simple and super useful :)