Spread Operator – How Spread Works in JavaScript
The spread operator (...
) helps you expand iterables into individual elements.
The spread syntax serves within array literals, function calls, and initialized properties object to spread the values of iterable objects into separate items. So effectively, it does the opposite thing from the rest operator.
A spread syntax is effective only when used within array literals, function calls, or initialized properties objects.
So, what exactly does this mean? Let's see with some examples.
Spread Example 1: How Spread Works in an Array Literal
const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];
console.log(aboutMe);
// The invocation above will return:
// ["Oluwatobi", "Sofela", "is", "my", "name."]
The snippet above used spread (...
) to copy myName
array into aboutMe
.
- Alterations to
myName
will not reflect inaboutMe
because all the values insidemyName
are primitives. Therefore, the spread operator simply copied and pastedmyName
's content intoaboutMe
without creating any reference back to the original array. - As mentioned by @nombrekeff in the DEV comment, the spread operator only does shallow copy. So, keep in mind that supposing
myName
contained any non-primitive value, the computer would have created a reference betweenmyName
andaboutMe
. See info 3 for more on how the spread operator works with primitive and non-primitive values. - Suppose we did not use the spread syntax to duplicate
myName
's content. For instance, if we had writtenconst aboutMe = ["Oluwatobi", myName, "name."]
. In such a case, the computer would have assigned a reference back tomyName
. As such, any change made in the original array would reflect in the duplicated one.
Spread Example 2: How to Use Spread to Convert a String into Individual Array Items
const myName = "Oluwatobi Sofela";
console.log([...myName]);
// The invocation above will return:
//["O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a"]
In the snippet above, we used the spread syntax (...
) within an array literal object ([...]
) to expand myName
's string value into individual items.
As such, "Oluwatobi Sofela"
got expanded into [ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]
.
Spread Example 3: How the Spread Operator Works in a Function Call
const numbers = [1, 3, 5, 7];
function addNumbers(a, b, c, d) {
return a + b + c + d;
}
console.log(addNumbers(...numbers));
// The invocation above will return: 16
In the snippet above, we used the spread syntax to spread the numbers
array's content across addNumbers()
's parameters.
Suppose the numbers
array had more than four items. In such a case, the computer will only use the first four items as addNumbers()
argument and ignore the rest.
Here's an example:
const numbers = [1, 3, 5, 7, 10, 200, 90, 59];
function addNumbers(a, b, c, d) {
return a + b + c + d;
}
console.log(addNumbers(...numbers));
// The invocation above will return: 16
Here's another example:
const myName = "Oluwatobi Sofela";
function spellName(a, b, c) {
return a + b + c;
}
console.log(spellName(...myName)); // returns: "Olu"
console.log(spellName(...myName[3])); // returns: "wundefinedundefined"
console.log(spellName([...myName])); // returns: "O,l,u,w,a,t,o,b,i, ,S,o,f,e,l,aundefinedundefined"
console.log(spellName({ ...myName })); // returns: "[object Object]undefinedundefined"
Spread Example 4: How Spread Works in an Object Literal
const myNames = ["Oluwatobi", "Sofela"];
const bio = { ...myNames, runs: "codesweetly.com" };
console.log(bio);
// The invocation above will return:
{0: "Oluwatobi", 1: "Sofela", runs: "codesweetly.com"}
In the snippet above, we used spread inside the bio
object to expand myNames
values into individual properties.
So now that we know what the spread operator is, we can talk about what makes it different from the rest operator.
Rest vs. Spread Operator: What's the Difference?
JavaScript uses three dots (...
) for both the rest and spread operators. But these two operators are not the same.
The main difference between rest and spread is that the rest operator puts the rest of some specific user-supplied values into a JavaScript array. But the spread syntax expands iterables into individual elements.
For instance, consider this code that uses rest to enclose some values into an array:
// Use rest to enclose the rest of specific user-supplied values into an array:
function myBio(firstName, lastName, ...otherInfo) {
return otherInfo;
}
// Invoke myBio function while passing five arguments to its parameters:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");
// The invocation above will return:
// ["CodeSweetly", "Web Developer", "Male"]
In the snippet above, we used the ...otherInfo
rest parameter to encase "CodeSweetly"
, "Web Developer"
, and "Male"
into an array.
Now, consider this example of a spread operator:
// Define a function with three parameters:
function myBio(firstName, lastName, company) {
return `${firstName} ${lastName} runs ${company}`;
}
// Use spread to expand an array's items into individual arguments:
myBio(...["Oluwatobi", "Sofela", "CodeSweetly"]);
// The invocation above will return: "Oluwatobi Sofela runs CodeSweetly"
In the snippet above, we used the spread operator (...
) to spread ["Oluwatobi", "Sofela", "CodeSweetly"]
's content across myBio()
's parameters.
Important Stuff to Know about the Spread Operator
Keep these three essential pieces of info in mind whenever you choose to use the spread operator.
Info 1: Spread operators can't expand object literal's values
Since a properties object is not an iterable object, you cannot use the spread operator to expand its values.
However, you can use the spread operator to clone properties from one object into another.
Here's an example:
const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName, website: "codesweetly.com" };
console.log(bio);
// The invocation above will return:
{firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com"}
The snippet above used the spread operator to clone myName
's content into the bio
object.
- The spread operator can expand iterable objects' values only.
- An object is iterable only if it (or any object in its prototype chain) has a property with a @@iterator key.
- Array, TypedArray, String, Map, and Set are all built-in iterable types because they have the
@@iterator
property by default. - A properties object is not an iterable data type because it does not have the
@@iterator
property by default. - You can make a properties object iterable by adding
@@iterator
onto it.
Info 2: The spread operator does not clone identical properties
Suppose you used the spread operator to clone properties from object A into object B. And suppose object B contains properties identical to those in object A. In such a case, B's versions will override those inside A.
Here's an example:
const myName = { firstName: "Tobi", lastName: "Sofela" };
const bio = { ...myName, firstName: "Oluwatobi", website: "codesweetly.com" };
console.log(bio);
// The invocation above will return:
{firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com"}
Observe that the spread operator did not copy myName
's firstName
property into the bio
object because bio
already contains a firstName
property.
Info 3: Beware of how spread works on objects containing non-primitives!
Suppose you used the spread operator on an object (or array) containing only primitive values. The computer will not create any reference between the original object and the duplicated one.
For instance, consider this code below:
const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];
console.log(aboutMe);
// The invocation above will return: ["Oluwatobi", "Sofela", "is", "my", "name."]
Observe that every item in myName
is a primitive value. Therefore, when we used the spread operator to clone myName
into aboutMe
, the computer did not create any reference between the two arrays.
As such, any alteration you make to myName
will not reflect in aboutMe
, and vice versa.
As an example, let's add more content to myName
:
myName.push("real");
Now, let's check the current state of myName
and aboutMe
:
console.log(myName); // ["Sofela", "is", "my", "real"]
console.log(aboutMe); // ["Oluwatobi", "Sofela", "is", "my", "name."]
Notice that myName
's updated content did not reflect in aboutMe
—because spread created no reference between the original array and the duplicated one.
What if myName
contains non-primitive items?
Suppose myName
contained non-primitives. In that case, spread will create a reference between the original non-primitive and the cloned one.
Here is an example:
const myName = [["Sofela", "is", "my"]];
const aboutMe = ["Oluwatobi", ...myName, "name."];
console.log(aboutMe);
// The invocation above will return:
// ["Oluwatobi", ["Sofela", "is", "my"], "name."]
Observe that myName
contains a non-primitive value.
Therefore, using the spread operator to clone myName
's content into aboutMe
caused the computer to create a reference between the two arrays.
As such, any alteration you make to myName
's copy will reflect in aboutMe
's version, and vice versa.
As an example, let's add more content to myName:
myName[0].push("real");
Now, let's check the current state of myName
and aboutMe
:
console.log(myName); // [["Sofela", "is", "my", "real"]]
console.log(aboutMe); // ["Oluwatobi", ["Sofela", "is", "my", "real"], "name."]
Notice that myName
's updated content is reflected in aboutMe
—because spread created a reference between the original array and the duplicated one.
Here's another example:
const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName };
myName.firstName = "Tobi";
console.log(myName); // { firstName: "Tobi", lastName: "Sofela" }
console.log(bio); // { firstName: "Oluwatobi", lastName: "Sofela" }
In the snippet above, myName
's update did not reflect in bio
because we used the spread operator on an object that contains primitive values only.
A developer would call myName
a shallow object because it contains only primitive items.
Here's one more example:
const myName = {
fullName: { firstName: "Oluwatobi", lastName: "Sofela" },
};
const bio = { ...myName };
myName.fullName.firstName = "Tobi";
console.log(myName); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }
console.log(bio); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }
In the snippet above, myName
's update is reflected in bio
because we used the spread operator on an object that contains a non-primitive value.
- We call
myName
a deep object because it contains a non-primitive item. - You do shallow copy when you create references while cloning one object into another. For instance,
...myName
produces a shallow copy of themyName
object because whatever alteration you make in one will reflect in the other. - You do deep copy when you clone objects without creating references. For instance, I could deep copy
myName
intobio
by doingconst bio = JSON.parse(JSON.stringify(myName))
. By so doing, the computer will clonemyName
intobio
without creating any reference. - You can break off the reference between the two objects by replacing the
fullName
object insidemyName
orbio
with a new object. For instance, doingmyName.fullName = { firstName: "Tobi", lastName: "Sofela" }
would disconnect the pointer betweenmyName
andbio
.
Overview
This article discussed what the spread operator is. We also looked at how spread works in array literals, function calls, and object literals.