loading...
Cover image for What Javascript Spread Operator is, How It Works and How to Use It

What Javascript Spread Operator is, How It Works and How to Use It

alexdevero profile image Alex Devero Originally published at blog.alexdevero.com ・9 min read

JavaScript spread operator is one of the more popular features that were introduced in ES6. This tutorial will help you understand it. You will learn what spread operator is and how it works. You will also learn how to use it to copy and merge arrays and object literals, insert data and more.

Introduction to JavaScript spread operator

Spread operator is a feature that allows you to access content of an iterable object. Iterable object is an object, or data structure, that allows to access its content with for...of loop. The most popular example of an iterable is an array. Another example of an iterable can be objects literals or strings.

When you wanted to access all content in some iterable, before spread operator was a thing, you had to use some kind of a loop, such as the mentioned for...of loop, or method, such as forEach(). Another option were indexes. Spread Operator allows you to do this much faster and with much less code. About the syntax.

The syntax of spread operator is simple and easy to remember. It consists of three dots (...). These three dots are followed by the iterable (...someIterable), whose content you want to access.

// Create array
const myArray = ['Venus', 'Ares', 'Mars']

// Use spread operator to access content of "myArray" variable
// Syntax: ...someIterable
console.log(...myArray)
// Output:
// 'Venus' 'Ares' 'Mars'

Spread operator and object literals

When you want to use spread operator with object literals the syntax is the same. You will use those three dots, but now followed by the name of the object whose content you want to access. The result you will get will be the content, only without the surrounding curly braces.

// Create object literal
const myObj = {
  firstName: 'Sam',
  lastName: 'Dodge'
}

// Use spread operator to access content of "myObj" variable
// Note: the {} around ...myObj are to avoid TypeError
// console.log(...myObj) would not work
console.log({ ...myObj })
// Output:
// { firstName: 'Sam', lastName: 'Dodge' }

Duplicating iterables with spread operator

The spread operator allows to quickly access content of an iterable. This can be useful when you want to copy iterable objects. You may not know this, but copying objects can be tricky. When you try to copy some primitive, like a number or a string, you will create a real copy, or clone. This is called deep copy.

Deep and shallow copies

This is not true for objects, including iterables. When you try to copy an object, such as array, you will not create a real copy. What will happen instead is that JavaScript will create new reference for that object. You can think about this as creating new alias or name.

When you copy an object, you only create new alias for it. As a result, you have two names for that thing, that object. However, there is still only one object, not two. This also means that if you do something with the object using the second alias (reference), those changes will also have effect on the first alias.

Remember, there is still only one objects, but two references (aliases) to access it. This type of copy is called shallow copy and this type of copying is called copying by reference.

// Create an array
const myArray = ['Spread', 'Rest', 'JavaScript']

// Create shallow copy of "myArray" variable
const myShallowCopy = myArray

// Log the content of "myArray"
console.log(myArray)
// Output:
// [ 'Spread', 'Rest', 'JavaScript' ]

// Log the content of "myShallowCopy"
console.log(myShallowCopy)
// Output:
// [ 'Spread', 'Rest', 'JavaScript' ]

// Remove the last item from the original array ("myArray")
myArray.pop()

// Log the content of "myArray" again
// The last item is gone as it should
console.log(myArray)
// Output:
// [ 'Spread', 'Rest' ]

// Log the content of "myShallowCopy" again
// The change of "myArray" will also appear in "myShallowCopy"
// The last item is also gone
console.log(myShallowCopy)
// Output:
// [ 'Spread', 'Rest' ]

Deep copies with spread operator

This is how copying in JavaScript works automatically. The good news is that spread operator allows you to overcome this issue with shallow copies. It allows you to quickly create deep copies of iterables. So, no more worries about changes happening in unexpected places.

Creating a real, deep, copy of some iterable with spread operator is simple. First, create a variable and assign it some iterable, some array. This will be the iterable you will copy. Second, create new variable. To assign this new variable a copy of the first you will use the spread operator followed by the name of first variable, wrapped with square brackets.

The spread operator will access the content and basically strip away the square brackets of the original array. So, in order to re-create the array, you will wrap the content in new pair of square brackets. That's it. You have a new deep copy of the first iterable, in this case the original array.

If you decide to change the original array, or the copy, that change will have effect only for that specific array.

// Create the original array
const myArray = ['Spread', 'Rest', 'JavaScript']

// Use spread operator to create deep copy of "myArray""
const myDeepCopy = [...myArray]

// Log the content of "myArray"
console.log(myArray)
// Output:
// [ 'Spread', 'Rest', 'JavaScript' ]

// Log the content of "myDeepCopy"
console.log(myDeepCopy)
// Output:
// [ 'Spread', 'Rest', 'JavaScript' ]

// Remove the last item from the original array "myArray"
myArray.pop()

// Log the content of "myArray" again
// The last item is gone as it should
console.log(myArray)
// Output:
// [ 'Spread', 'Rest' ]

// Log the content of "myDeepCopy" again
// The "myDeepCopy" is not affected by change made to "myArray"
// The last item is still there as it should
console.log(myDeepCopy)
// Output:
// [ 'Spread', 'Rest', 'JavaScript' ]

Deep copies of object literals with spread operator

Just like you can create deep copies of arrays you can also create deep copies of object literals. The syntax is almost the same. You have to use those three dots followed by the name of the object literal you want to copy. You will then assign this into a new variable. Just make sure to wrap this whole thing in curly brackets, not square.

// Create the original array
const myObj = {
  title: 'Guards! Guards!',
  author: 'Terry Pratchett',
}

// Use spread operator to create deep copy of "myObj""
const myDeepCopy = { ...myObj }

// Log the content of "myObj"
console.log(myObj)
// Output:
// { title: 'Guards! Guards!', author: 'Terry Pratchett' }

// Log the content of "myDeepCopy"
console.log(myDeepCopy)
// Output:
// { title: 'Guards! Guards!', author: 'Terry Pratchett' }

// Add new property the original object "myObj"
myObj.format = 'Hardcover'

// Log the content of "myObj" again
// New property is there
console.log(myObj)
// Output:
// {
//   title: 'Guards! Guards!',
//   author: 'Terry Pratchett',
//   format: 'Hardcover'
// }

// Log the content of "myDeepCopy" again
// The "myDeepCopy" still contains only two properties
console.log(myDeepCopy)
// Output:
// { title: 'Guards! Guards!', author: 'Terry Pratchett' }

Note: Creating deep copies with spread operator will work only for first-level items. It will not work for nested arrays ad objects. For this, you can use const myClone = JSON.parse(JSON.stringify(objToClone)).

Merging with spread operator

Another thing you can do with spread operator is merging two or more iterables. Previously, when you wanted to merge two or more arrays for example you would have to use some method such as concat(). Spread operator allows you to do this just as fast. If not faster. With easier syntax.

The process is similar to copying existing array. You create new array. Next, you use the spread operator along with the names of first array you want to merge. This array will be followed by comma and another spread followed by name of the second array. Finally, you will also wrap this inside a pair square brackets.

The result you will get will be all items from the arrays you wanted to merge inside a single array.

// Create first array
const arrayOne = [1, 2, 3]

// Create second array
const arrayTwo = ['four', 'five', 'six']

// Merge first and second array using spread operator
// Syntax: [...arrayOne, ...arrayTwo, ...arrayThree, etc.]
const arrayMerged = [...arrayOne, ...arrayTwo]

// Log the content of "arrayMerged"
console.log(arrayMerged)
// Output:
// [ 1, 2, 3, 'four', 'five', 'six' ]

Merging object literals with spread operator

Merging object literals with spread operator works just like with arrays. The only difference in syntax is that you have to use curly brackets instead of square brackets to wrap everything. The rest is the same and the result is a new object literal with merged content of the object literals you specified.

// Create first object
const myObjOne = {
  title: 'The Color of Magic',
  author: 'Terry Pratchett',
}

// Create second object
const myObjTwo = {
  publicationDate: '2009',
  format: 'Paperback'
}

// Create third object
const myObjThree = {
  language: 'English',
  genre: 'Fantasy'
}

// Use spread operator to merge "myObjOne", "myObjTwo" and "myObjThree"
const myMergedObj = { ...myObjOne, ...myObjTwo, ...myObjThree }

// Log the content of "myMergedObj"
console.log(myMergedObj)
// Output:
// {
//   title: 'The Color of Magic',
//   author: 'Terry Pratchett',
//   publicationDate: '2009',
//   format: 'Paperback',
//   language: 'English',
//   genre: 'Fantasy'
// }

Inserting data with spread operator

We discussed how to use spread operator to access content of arrays and object literals. We also discussed how to use it to duplicate those iterables and even merge them. This is not all you can do. You can also use spread operator to data. You can take content of one iterable and insert it inside another iterable.

For example, let's say you have two arrays with some content. Spread operator allows you to insert the content of one anywhere inside the second. You can do this also with object literals, insert content from one anywhere inside another. Or, you can insert object literal into an array or vice versa.

// Example no.1: Arrays
// Create first array
const myArrayOne = ['C', 'C++', 'Java']

// Create second array with some content
// plus all the content from "myArrayOne"
const myArrayTwo = ['JavaScript', 'Python', 'PHP', ...myArrayOne, 'Assembly']

// Log the content of "myArrayTwo"
console.log(myArrayTwo)
// Output:
// [ 'JavaScript', 'Python', 'PHP', 'C', 'C++', 'Java', 'Assembly' ]


// Example no.2: Object literals
// Create first object literal
const myObjOne = {
  numOfPages: 706,
  publisher: 'O\'Reilly Media'
}

// Create second object literal with some content
// plus all the content from "myObjOne"
const myObjTwo = {
  title: 'JavaScript: The Definitive Guide',
  author: 'David Flanagan',
  ... myObjOne, // insert the content of "myObjOne"
  language: 'English'
}

// Log the content of "myObjTwo"
console.log(myObjTwo)
// Output:
// {
//   title: 'JavaScript: The Definitive Guide',
//   author: 'David Flanagan',
//   numOfPages: 706,
//   publisher: "O'Reilly Media",
//   language: 'English'
// }

Spread operator and function arguments

When you use spread operator to access content of an iterable, it will return only the content. It will remove the surrounding square brackets in case of an array or curly brackets in case of an object literal. This can be handy when you want to call a function that takes some arguments.

Instead of passing each argument one by one, you can pass in an array with all arguments preceded by spread operator. The result will be the same as if you would pass all arguments one by one.

// Create an array with something
// that will be used as arguments
const myArgs = ['Jack', 'Joe', 'Tony']

// Create a simple function
// that will return all arguments one by one
function sayNames(name1, name2, name3) {
  return `Names you passed are ${name1}, ${name2} and ${name3}.`
}

// Call sayNames() function using spread operator
// to pass in content of "myArgs" as arguments
sayNames(...myArgs)
// Output:
// 'Names you passed are Jack, Joe and Tony.'

You can also use spread operator with math functions, that accept multiple values as arguments, to pass in those values.

// Create an array with numbers
const numbers = [15, 3, -5, 84, 653, Infinity]

// Get the highest number inside "numbers"
const highestNumber = Math.max(...numbers)

// Get the lowest number inside "numbers"
const lowestNumber = Math.min(...numbers)

// Log the value of "highestNumber"
console.log(highestNumber)
// Output:
// Infinity

// Log the value of "lowestNumber"
console.log(lowestNumber)
// Output:
// -5

Using spread operator with strings

In the beginning of this tutorial we discussed that spread operator can be used with iterable objects. Well, only with them. I mentioned that one of these iterables are also strings. This may sound weird, but it is true. JavaScript allows you to use spread operator also with strings.

When you use spread on a string, the result will be the same as if you would use split() method. In other words, spread operator will convert the string into an array. You will get that string in the form of individual characters, that is words, digits and whitespace.

// Create some text
const text = 'Sunny day.'

// Use spread to convert the string into an array
const processedText = [...text]

// Log the content
console.log(processedText)
// Output:
// [ 'S', 'u', 'n', 'n', 'y', ' ', 'd', 'a', 'y', '.' ]

Conclusion: What JavaScript spread operator is, how it works and how to use it

JavaScript spread operator is very easy to use. It allows you to do a lot of things with only small amount of code. I hope this tutorial helped you understand what JavaScript spread operator is, how it works and how to use it.

Posted on by:

alexdevero profile

Alex Devero

@alexdevero

I am founder/CEO of DEVERO Corporation. Entrepreneur, developer, programmer, hacker, designer and blogger from Czechia.

Discussion

pic
Editor guide
 

It doesn't actually create a deep copy.

const arr1 = [ { name: 'Abe' } ];
const arr2 = [...arr1];
arr2[0].name = 'Ben';
console.log(arr1[0].name); // Ben

The same is true with objects. It just copies the first level of properties to the new iterable. If the values are primitives, it copies by value, but if they're objects it copies by reference.

 

I can't find anything about copy by reference in the ecmascript standard.

ecma-international.org/publication...

Can you show me what I'm missing?

What I do see in the standard is that the value of the object is copied, but that this is separate to the properties of the object -- is this what you're calling copy by reference?

 

The spec does not say it copies the value of the object, it says "keys and their values are copied onto a new object". tc39.es/proposal-object-rest-spread/

It is well know that making a "copy" of a primitive copies the value and making a "copy" of an object also copies the value, but the value is a reference to that object and not a clone of the object. This is what people refer to when they say copy "by reference" and is the source of the value vs reference debate which turns out to be a mostly semantic debate. JS copies by value but sometimes the value is a reference.

The proposal for this operator specifically calls it a "shallow clone (excluding prototype)" and it's just syntactic sugar for Object.assign() github.com/tc39/proposal-object-re...

Object.assign is 19.1.2.1 in the spec and you'll see there's no recursion and therefore it's not a deep copy/clone. It loops through the OwnPropertyKeys once and does a Get from the source and a Set on the target. It does not evaluate the OwnPropertyKeys of the value if the value is an object.

 

Hi. I wrote about this topic of deep and shallow copies, or copy by value and copy by reference, a few months ago. Here is the link to that article: blog.alexdevero.com/shallow-deep-c...

Ok, I think I see the argument that you're making, but I don't think that it is sound.

A reference isn't a shallow copy -- it's a way to reach the original thing.

And pass by value doesn't imply a deep copy -- two distinct values can be used to indirectly access the same sub-structures.

You make the claim that:

This is not true for objects, including iterables. When you try to copy an object, such as array, you will not create a real copy. What will happen instead is that JavaScript will create new reference for that object. You can think about this as creating new alias or name.

Can you point me to where in the ecmascript standard it talks about creating a new reference for an object?

Or do you mean that it 'passes the value of the object, which allows access to the associated properties, making this a shallow copy of the original'?

This argument seems more reasonable -- but it doesn't involve any references or copy by reference -- it's simply copy by value, where the properties of the object are indirectly accessed by the object value.

But if you can show me where I've missed object references in the language specification, I'd be grateful.

What he calls a "shallow copy" in the article, I'd call an alias.

let obj2 = obj1

This makes "obj2" a "reference" to the same object as "obj1", at the same memory address. Yes, obj2 gets a new slice of memory but only to store a reference to a memory address where the object is stored (i.e. it copies the reference memory address from obj1 to obj2). If obj1 was a primitive, though, it would be a literal copy of the value into a new memory address.

The object spread operator, or Object.assign() creates what I'd call a "shallow copy". It basically performs the "alias" operation from above on each property of obj1 into obj2

let obj2 = {...obj1}

In the case of primitive values for keys of obj1, just like the = assignment, you get a literal copy of the value. But for object values, you get the same memory address / reference / alias behavior.

The third type of copy, a deep copy / clone, is not built in to the language. This is a recursive copy where object values from obj1 are recursively evaluated in the same way as the top level properties and copied "by value" (once a primitive value is finally reached) to the corresponding place in obj2. You can fairly easily write a recursive deep copy function or use Lodash cloneDeep lodash.com/docs/#cloneDeep if that is your desire, but that's not what the spread operator does.

let obj2 = obj1;

This initializes obj2 with the value of obj1.

So I guess you're writing "reference" because you agree that it isn't actually a reference?

1. Let bindingId be StringValue of BindingIdentifier.
2. Let lhs be ResolveBinding(bindingId).
3. If IsAnonymousFunctionDefinition(Initializer) is true, then
a. Let value be NamedEvaluation of Initializer with argument bindingId.
4. Else,
a. Let rhs be the result of evaluating Initializer.
b. Let value be ? GetValue(rhs).
5. Return InitializeReferencedBinding(lhs, value).

What you may notice here is that there is no distinction between primitive and object types -- in both cases we get the value and initialize the variable with it.

I think the confusion in these cases comes from a mistaken belief that the properties of an object are part of the object's value -- but this clearly isn't the case in the language specification.

There is a distinction. Read the GetValue definition and follow it down.

In any case, we're down a semantic hole about the language spec. My example in my first comment clearly shows that spread doesn't create a deep copy.

1. ReturnIfAbrupt(V).
2. If Type(V) is not Reference, return V.
3. Let base be GetBase(V).
4. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
5. If IsPropertyReference(V) is true, then
a. If HasPrimitiveBase(V) is true, then
i. Assert: In this case, base will never be undefined or null.
ii. Set base to ! ToObject(base).
b. Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)).
6. Else,
a. Assert: base is an Environment Record.
b. Return ? base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).

I'm not seeing the relevant distinction -- could you point it out?

No, I'm not going to read the spec for you.

I did the reading.

There's nothing there that distinguishes between primitive and object values.

So, I think you're mistaken and am giving you the opportunity to reconsider your claim.

If you do a behavior test in any interpreter there is obviously a difference. I'm pretty sure not every JS interpreter developer is mistaken. There is a clear difference but we've gone from me saying "hey, that's not a deep copy" to you asking me to point out the intricacies of the JS spec. Sorry, read it again. Every JS interpreter dev can't be wrong and you're right.

What is the obvious difference?

Every JS interpreter I've used copies the value of the object exactly as I expect, given my understanding of the spec.

Noting that the spec differentiates between an object value and the properties associated with that object value.

Please clarify the behavior test that you have in mind so that it can be tested.

You're moving the goal posts. I said it distinguishes between an object and primitives. That is demonstrated by the example in my first comment, which is why it's not a deep copy. Every interpreter runs my example the same way, allowing you to mutate the original using the copy.

Every interpreter since I started writing JS in the 1990s treats objects and primitives differently. There are countless articles over the last 20 years about that fact. You can't really believe that everyone is wrong. Given that, it must be you who misunderstands the spec. So I suggest you look up all the definitions of things used inside GetValue. If you look at it long enough, you'll see that it quite clearly follows a different path for primitive values than it does for objects.

What's the distinction that it makes?

JS doesn't allow you to mutate values at all -- and there's no difference between objects and values in that regard.

The only things you can mutate in JS are properties and variables.

(And this is one of the reasons that properties are not part of the object's value)

 

Thank you Jeremy for pointing this out. You are correct. Spread will create deep copy only for the top-level properties. In other cases, combination of JSON.parse() and JSON.stringify will create a deep copy.