- 6. Rest/Spread Properties
- 6.1. The rest operator (…) in object destructuring
- 6.2. The spread operator (…) in object literals
- 6.3. Common use cases for the object spread operator
- 6.4. Spreading objects versus Object.assign()
6. Rest/Spread Properties
The ECMAScript proposal “Rest/Spread Properties” by Sebastian Markbåge enables:
The rest operator (
…
) in object destructuring. At the moment, this operator only works for Array destructuring and in parameter definitions.The spread operator (
…
) in object literals. At the moment, this operator only works in Array literals and in function and method calls.
6.1. The rest operator (…) in object destructuring
Inside object destructuring patterns, the rest operator (…
) copies all enumerable own properties of the destructuring source into its operand, except those that were already mentioned in the object literal.
If you are using object destructuring to handle named parameters, the rest operator enables you to collect all remaining parameters:
6.1.1. Syntactic restrictions
Per top level of each object literal, you can use the rest operator at most once and it must appear at the end:
You can, however, use the rest operator several times if you nest it:
6.2. The spread operator (…) in object literals
Inside object literals, the spread operator (…
) inserts all enumerable own properties of its operand into the object created via the literal:
> const obj = {foo: 1, bar: 2};
> {...obj, baz: 3}
{ foo: 1, bar: 2, baz: 3 }
Note that order matters even if property keys don’t clash, because objects record insertion order:
> {baz: 3, ...obj}
{ baz: 3, foo: 1, bar: 2 }
If keys clash, order determines which entry “wins”:
> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, foo: true}
{ foo: true, bar: 2, baz: 3 }
> {foo: true, ...obj}
{ foo: 1, bar: 2, baz: 3 }
6.3. Common use cases for the object spread operator
In this section, we’ll look at things that you can use the spread operator for. I’ll also show how to do these things via Object.assign()
, which is very similar to the spread operator (we’ll compare them in more detail later).
6.3.1. Cloning objects
Cloning the enumerable own properties of an object obj
:
The prototypes of the clones are always Object.prototype
, which is the default for objects created via object literals:
> Object.getPrototypeOf(clone1) === Object.prototype
true
> Object.getPrototypeOf(clone2) === Object.prototype
true
> Object.getPrototypeOf({}) === Object.prototype
true
Cloning an object obj
, including its prototype:
Note that proto
inside object literals is only a mandatory feature in web browsers, not in JavaScript engines in general.
6.3.2. True clones of objects
Sometimes you need to faithfully copy all own properties of an object obj
and their attributes (writable
, enumerable
, …), including getters and setters. Then Object.assign()
and the spread operator don’t work. You need to use property descriptors:
If you additionally want to preserve the prototype of obj
, you can use Object.create()
:
Object.getOwnPropertyDescriptors()
is explained in “Exploring ES2016 and ES2017”.
6.3.3. Pitfall: cloning is always shallow
Keep in mind that with all the ways of cloning that we have looked at, you only get shallow copies: If one of the original property values is an object, the clone will refer to the same object, it will not be (recursively, deeply) cloned itself:
6.3.4. Various other use cases
Merging two objects obj1
and obj2
:
Filling in defaults for user data:
Specifying the default values for properties foo
and bar
inline:
Non-destructively updating property foo
:
6.4. Spreading objects versus Object.assign()
The spread operator and Object.assign()
are very similar. The main difference is that spreading defines new properties, while Object.assign()
sets them. What exactly that means is explained later.
6.4.1. The two ways of using Object.assign()
There are two ways of using Object.assign()
:
First, destructively (an existing object is changed):
Here, target
is modified; source1
and source2
are copied into it.
Second, non-destructively (no existing object is changed):
Here, a new object is created via an empty object literal and source1
and source2
are copied into it. At the end, this new object is returned and assigned to result
.
The spread operator is very similar to the second way of using Object.assign()
. Next, we’ll look at where the two are similar and where they differ.
6.4.2. Both spread and Object.assign() read values via a “get” operation
Both operations use normal “get” operations to read property values from the source, before writing them to the target. As a result, getters are turned into normal data properties during this process.
Let’s look at an example:
original
has the getter foo
(its property descriptor has the properties get
and set
):
> Object.getOwnPropertyDescriptor(original, 'foo')
{ get: [Function: foo],
set: undefined,
enumerable: true,
configurable: true }
But it its clones clone1
and clone2
, foo
is a normal data property (its property descriptor has the properties value
and writable
):
> const clone1 = {...original};
> Object.getOwnPropertyDescriptor(clone1, 'foo')
{ value: 123,
writable: true,
enumerable: true,
configurable: true }
> const clone2 = Object.assign({}, original);
> Object.getOwnPropertyDescriptor(clone2, 'foo')
{ value: 123,
writable: true,
enumerable: true,
configurable: true }
6.4.3. Spread defines properties, Object.assign() sets them
The spread operator defines new properties in the target, Object.assign()
uses a normal “set” operation to create them. That has two consequences.
6.4.3.1. Targets with setters
First, Object.assign()
triggers setters, spread doesn’t:
The previous piece of code installs a setter foo
that is inherited by all normal objects.
If we clone obj
via Object.assign()
, the inherited setter is triggered:
> Object.assign({}, obj)
SET 123
{}
With spread, it isn’t:
> { ...obj }
{ foo: 123 }
Object.assign()
also triggers own setters during copying, it does not overwrite them.
6.4.3.2. Targets with read-only properties
Second, you can stop Object.assign()
from creating own properties via inherited read-only properties, but not the spread operator:
The previous piece of code installs the read-only property bar
that is inherited by all normal objects.
As a consequence, you can’t use assignment to create the own property bar
, anymore (you only get an exception in strict mode; in sloppy mode, setting fails silently):
> const tmp = {};
> tmp.bar = 123;
TypeError: Cannot assign to read only property 'bar'
In the following code, we successfully create the property bar
via an object literal. This works, because object literals don’t set properties, they define them:
However, Object.assign()
uses assignment for creating properties, which is why we can’t clone obj
:
> Object.assign({}, obj)
TypeError: Cannot assign to read only property 'bar'
Cloning via the spread operator works:
> { ...obj }
{ bar: 123 }
6.4.4. Both spread and Object.assign() only consider own enumerable properties
Both operations ignore all inherited properties and all non-enumerable own properties.
The following object obj
inherits one (enumerable!) property from proto
and has two own properties:
If you clone obj
, the result only has the property ownEnumerable
. The properties inheritedEnumerable
and ownNonEnumerable
are not copied:
> {...obj}
{ ownEnumerable: 2 }
> Object.assign({}, obj)
{ ownEnumerable: 2 }