Sebastian Markbage’s Rest/Spread Properties proposal consists of two parts:

  • Rest operators for object deconstruction (…) . Currently, this operator can only be used in array deconstruction and parameter definitions
  • The spread operator (…) in the object literal . Currently, this operator can only be used on array literals and in function methods.

Rest operators in object deconstruction (…)

In object deconstruction mode, the REST operator copies all enumerable properties of the deconstructed source into its operand, in addition to those already specified 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};Copy the code

If you are using object deconstruction to handle named arguments, the REST operator lets you collect all remaining arguments:

functionfunc({param1, param2, ... rest}) { // rest operator console.log('All parameters: ',{param1, param2, ... rest}); // spread operatorreturn param1 + param2;
}
Copy the code

Grammar limited

The REST operator can be used at most once at the top level of each object literal, and must appear only at the end:

const {... rest, foo} = obj; // SyntaxError const {foo, ... rest1, ... rest2} = obj; // SyntaxErrorCopy the code

If the structure is nested, you can use the REST operator multiple times:

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};Copy the code

The spread operator in the object literal

Inside the object literal, the spread operator inserts all the enumerable properties of its operand into the object created from the literal:

> const obj = {foo: 1, bar: 2, baz: 3}; > {... obj, qux: 4} { foo: 1, bar: 2, baz: 3, qux: 4 }Copy the code

Note the order, even if the attribute key does not conflict, because the object records the insertion order:

> {qux: 4, ... obj} { qux: 4, foo: 1, bar: 2, baz: 3 }Copy the code

If there is a conflict with the key, the following attribute overrides the previous one:

> 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 }Copy the code

The use scenario for the spread operator object

In this section, we’ll look at a usage scenario for the spread operator. I’ll also implement this again with object.assign (), which is similar to the spread operator (we’ll compare them in more detail later).

Copy the object

Copy the enumerable property of obj:

const clone1 = {... obj}; constclone2 = Object.assign({}, obj);
Copy the code

Clone objects are all Object. Prototype, which is the default prototype for all objects created from Object literals:

> Object.getPrototypeOf(clone1) === Object.prototype
true
> Object.getPrototypeOf(clone2) === Object.prototype
true
> Object.getPrototypeOf({}) === Object.prototype
true
Copy the code

Copy an object obj, including its prototype:

const clone1 = {__proto__: Object.getPrototypeOf(obj), ... obj}; constclone2 = Object.assign(
    Object.create(Object.getPrototypeOf(obj)), obj);
Copy the code

Note that in general, proTo inside object literals is a built-in feature of the browser, not owned by the JavaScript engine.

A true copy of the object

Sometimes you need to copy all of your object’s own properties, writable, Enumerable… , including getters and setters. The object.assign () and spread operators are useless. You need to use the property descriptors:

const clone1 = Object.defineProperties({},
    Object.getOwnPropertyDescriptors(obj));
Copy the code

If you still want to keep the prototype of obj, you can use object.create () :

const clone2 = Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj));
Copy the code

“Explore ES2016 and ES2017” Object is introduced. The getOwnPropertyDescriptors ()

Catch: Always shallow copy

All of the ways we’ve seen copying objects before are shallow copies: if the original property value is an object, the copied object will refer to the same object, and it won’t copy itself (recursively, deeply) :

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
Copy the code

Other Usage Scenarios

Merge obj1 and obj2:

const merged = {... obj1, ... obj2}; const merged = Object.assign({}, obj1, obj2);Copy the code

Populate the user data with default values

const DEFAULTS = {foo: 'a', bar: 'b'}; const userData = {foo: 1}; const data = {... DEFAULTS, ... userData}; const data = Object.assign({}, DEFAULTS, userData); // {foo: 1, bar:'b'}
Copy the code

Safely update the property foo:

const obj = {foo: 'a', bar: 'b'}; const obj2 = {... obj, foo: 1}; const obj2 = Object.assign({}, obj, {foo: 1}); // {foo: 1, bar:'b'}
Copy the code

Specify the default values for the properties foo and bar:

const userData = {foo: 1};
const data = {foo: 'a', bar: 'b'. userData}; const data = Object.assign({}, {foo:'a', bar:'b'}, userData);
    // {foo: 1, bar: 'b'}
Copy the code

Expand Object vs. Object.assign()

The spread operator is similar to object.assign (). The main difference is that the former defines new attributes, while the latter also assigns values. I’ll explain what that means later.

Two ways to use object.assign ()

Object.assign() can be used in two ways: first, destructively (modifying an existing Object) :

Object.assign(target, source1, source2);
Copy the code

The target object here has been modified; Source1 and source2 are copied in. Second, non-destructive (existing objects will not be modified) :

const result = Object.assign({}, source1, source2);
Copy the code

The new object is generated by copying source1 and source2 into an empty object. Finally, the new object is returned and assigned to Result. The spread operator is similar to the second method of object.assign (). Next, let’s look at the similarities and differences.

All values are read through the “get” operator

Both use the “get” operator to read the property values of the source object before writing the object. This process converts the getter into a normal data property. Here’s an example:

const original = {
    get foo() {
        return123; }};Copy the code

Original has a getter for Foo (its property descriptors have get and set properties)

> Object.getOwnPropertyDescriptor(original, 'foo')
{ get: [Function: foo],
  set: undefined,
  enumerable: true,
  configurable: true }
Copy the code

But in clone1 and clone2, foo is a normal data property (property descriptors have value and writable properties) :

> 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 }
Copy the code

Spread defines the properties, object.assign () sets the properties

The spread operator defines new properties on the target Object, while Object.assign() uses a “set” operator to create properties. This results in two things:

The target object has a setter

First, object.assign () fires setters, while spread does not:

Object.defineProperty(Object.prototype, 'foo', {
    set(value) {
        console.log('SET', value); }}); const obj = {foo: 123};Copy the code

The above code snippet sets a setter for Foo, which is inherited by all normal objects. If we copy obj with object.assign (), the inherited setter will be fired:

> Object.assign({}, obj)
SET 123
{}
Copy the code

Spread does not:

> { ...obj }
{ foo: 123 }
Copy the code

Object.assign() also fires its own setter when copying; there is no overwriting.

The target object has a read-only property

Second, you can prevent object.assign () from creating its own attributes by inheriting read-only attributes, but you can’t do that with spread:

Object.defineProperty(Object.prototype, 'bar', {
    writable: false,
    value: 'abc'});Copy the code

The above code sets the read-only property bar, which is inherited by all normal objects. Thus, you can no longer use assignment statements to create your own property bar (strict mode throws an exception, loose mode silently fails) :

> const tmp = {};
> tmp.bar = 123;
TypeError: Cannot assign to read only property 'bar'
Copy the code

In the following code, we successfully create the property bar using an object literal. Because object literals don’t set properties, they just define them:

const obj = {bar: 123};
Copy the code

However, object.assign () creates attributes using an assignment statement, which is why obj cannot be copied:

> Object.assign({}, obj)
TypeError: Cannot assign to read only property 'bar'
Copy the code

There is no problem copying through the spread operator:

> { ...obj }
{ bar: 123 }
Copy the code

Spread and object.assign () both copy only their own enumerable attributes

They ignore all inherited properties and non-enumerable own properties. The object obj inherits an enumerable property from Proto and has two attributes of its own:

const proto = {
    inheritedEnumerable: 1,
};
const obj = Object.create(proto, {
    ownEnumerable: {
        value: 2,
        enumerable: true,
    },
    ownNonEnumerable: {
        value: 3,
        enumerable: false,}});Copy the code

If you copy obj, the result will only be the ownEnumerable property. InheritedEnumerable and ownNonEnumerable are not copied:

> {... obj} { ownEnumerable: 2 } > Object.assign({}, obj) { ownEnumerable: 2 }Copy the code

Original: http://exploringjs.com/es2018-es2019/ch_rest-spread-properties.html