Update 2018-01-25: This proposal has reached stage 4 and will be part of ECMAScript 2018.
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.
...
) 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.
const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;
// Same as:
// const foo = 1;
// const rest = {bar: 2, baz: 3};
If you are using object destructuring to handle named parameters, the rest operator enables you to collect all remaining parameters:
function func({param1, param2, ...rest}) { // rest operator
console.log('All parameters: ',
{param1, param2, ...rest}); // spread operator
return param1 + param2;
}
Per top level of each object literal, you can use the rest operator at most once and it must appear at the end:
const {...rest, foo} = obj; // SyntaxError
const {foo, ...rest1, ...rest2} = obj; // SyntaxError
You can, however, use the rest operator several times if you nest it:
const obj = {
foo: {
a: 1,
b: 2,
c: 3,
},
bar: 4,
baz: 5,
};
const {foo: {a, ...rest1}, ...rest2} = obj;
// Same as:
// const a = 1;
// const rest1 = {b: 2, c: 3};
// const rest2 = {bar: 4, baz: 5};
...
) 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, baz: 3};
> {...obj, qux: 4}
{ foo: 1, bar: 2, baz: 3, qux: 4 }
Note that order matters even if property keys don’t clash, because objects record insertion order:
> {qux: 4, ...obj}
{ qux: 4, foo: 1, bar: 2, baz: 3 }
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 }
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).
Cloning the enumerable own properties of an object obj
:
const clone1 = {...obj};
const clone2 = Object.assign({}, 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:
const clone1 = {__proto__: Object.getPrototypeOf(obj), ...obj};
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)), obj);
Note that __proto__
inside object literals is only a mandatory feature in web browsers, not in JavaScript engines in general.
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:
const clone1 = Object.defineProperties({},
Object.getOwnPropertyDescriptors(obj));
If you additionally want to preserve the prototype of obj
, you can use Object.create()
:
const clone2 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
Object.getOwnPropertyDescriptors()
is explained in “Exploring ES2016 and ES2017”.
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:
const original = { prop: {} };
const clone = Object.assign({}, original);
console.log(original.prop === clone.prop); // true
original.prop.foo = 'abc';
console.log(clone.prop.foo); // abc
Merging two objects obj1
and obj2
:
const merged = {...obj1, ...obj2};
const merged = Object.assign({}, obj1, obj2);
Filling in defaults for user data:
const DEFAULTS = {foo: 'a', bar: 'b'};
const userData = {foo: 1};
const data = {...DEFAULTS, ...userData};
const data = Object.assign({}, DEFAULTS, userData);
// {foo: 1, bar: 'b'}
Non-destructively updating property foo
:
const obj = {foo: 'a', bar: 'b'};
const obj2 = {...obj, foo: 1};
const obj2 = Object.assign({}, obj, {foo: 1});
// {foo: 1, bar: 'b'}
Specifying the default values for properties foo
and bar
inline:
const userData = {foo: 1};
const data = {foo: 'a', bar: 'b', ...userData};
const data = Object.assign({}, {foo:'a', bar:'b'}, userData);
// {foo: 1, bar: 'b'}
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.
Object.assign()
There are two ways of using Object.assign()
:
First, destructively (an existing object is changed):
Object.assign(target, source1, source2);
Here, target
is modified; source1
and source2
are copied into it.
Second, non-destructively (no existing object is changed):
const result = Object.assign({}, source1, source2);
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.
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:
const original = {
get foo() {
return 123;
}
};
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 }
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.
First, Object.assign()
triggers setters, spread doesn’t:
Object.defineProperty(Object.prototype, 'foo', {
set(value) {
console.log('SET', value);
},
});
const obj = {foo: 123};
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.
Second, you can stop Object.assign()
from creating own properties via inherited read-only properties, but not the spread operator:
Object.defineProperty(Object.prototype, 'bar', {
writable: false,
value: 'abc',
});
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:
const obj = {bar: 123};
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 }
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:
const proto = {
inheritedEnumerable: 1,
};
const obj = Object.create(proto, {
ownEnumerable: {
value: 2,
enumerable: true,
},
ownNonEnumerable: {
value: 3,
enumerable: false,
},
});
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 }