The Object Rest/Spread Proposal reached stage 4 in 2018, which means it will be included in a future iteration of the ECMAScript spec. It's also been included in Node.js LTS since Node.js 8, so you can safely start using it today.

$ node -v v8.9.4 $ node > const obj = { foo: 1, bar: 1 }; undefined > { ...obj, baz: 1 }; { foo: 1, bar: 1, baz: 1 }

The Object spread operator {...obj} is similar to Object.assign() , so which one should you use? Turns out the answer is a bit more nuanced than you might expect.

Quick Overview of Object Spread

The fundamental idea of the object spread operator is to create a new plain object using the own properties of an existing object. So {...obj} creates a new object with the same properties and values as obj . For plain old JavaScript objects, you're essentially creating a copy of obj .

const obj = { foo: 'bar' }; const clone = { ...obj }; obj.foo = 'baz' ; clone.foo;

Like Object.assign() , the object spread operator does not copy inherited properties or class information. It does copy ES6 symbols.

class BaseClass { foo() { return 1 ; } } class MyClass extends BaseClass { bar() { return 2 ; } } const obj = new MyClass(); obj.baz = function ( ) { return 3 ; }; obj[ Symbol .for( 'test' )] = 4 ; const clone = { ...obj }; console .log(clone); console .log(clone.constructor.name); console .log(clone instanceof MyClass);

You can also mix in other properties with the object spread operator. Order matters: the object spread operator will overwrite properties that are defined before it, but not after.

const obj = { a: 'a' , b: 'b' , c: 'c' }; { a: 1 , b: null , c: void 0 , ...obj }; { a: 1 , b: null , ...obj, c: void 0 }; { a: 1 , ...obj, b: null , c: void 0 }; { ...obj, a: 1 , b: null , c: void 0 };

Differences Versus Object.assign()

The Object.assign() function is essentially interchangeable with the object spread operator for the above examples. In fact, the object spread spec explicitly states that { ...obj } is equivalent to Object.assign({}, obj) .

const obj = { a: 'a' , b: 'b' , c: 'c' }; Object .assign({ a: 1 , b: null , c: void 0 }, obj); Object .assign({ a: 1 , b: null }, obj, { c: void 0 }); Object .assign({ a: 1 }, obj, { b: null , c: void 0 }); Object .assign({}, obj, { a: 1 , b: null , c: void 0 });

So why would you use one or the other? One key difference is that the object spread operator always gives you a POJO back. The Object.assign() function modifies its first parameter in place:

class MyClass { set val(v) { console .log( 'Setter called' , v); return v; } } const obj = new MyClass(); Object .assign(obj, { val: 42 });

In other words, Object.assign() modifies an object in place, and so it can trigger ES6 setters. If you prefer using immutable techniques, the object spread operator is a clear winner. With Object.assign() , you would have to ensure you always pass an empty object {} as the first argument.

Another related difference is that spread defines new properties, whereas Object.assign() sets them. For example, Object.assign() calls setters that are defined on Object.prototype , whereas the spread operator does not.

Object .defineProperty( Object .prototype, 'myProp' , { set: () => console .log( 'Setter called' ); }); const obj = { myProp: 42 }; Object .assign({}, obj); const newObj = { ..obj };

This is a fairly minor difference, because it is generally bad practice to define a custom setter on Object.prototype . But, you should note that Object.assign() calls setters on the target object.

const obj = {}; Object .defineProperty(obj, 'myProp' , { set: () => console .log( 'Setter called' ); }); Object .assign(obj, { myProp: 42 });

What about performance? Here's a couple simple benchmarks. It looks like object spread is faster if you pass an empty object as the first parameter to Object.assign() , but otherwise they're interchangeable.

Here's a benchmark using Object.assign() with in-place assignment:

const Benchmark = require ( 'benchmark' ); const suite = new Benchmark.Suite; const obj = { foo: 1 , bar: 2 }; suite. add( 'Object spread' , function ( ) { ({ baz: 3 , ...obj }); }). add( 'Object.assign()' , function ( ) { Object .assign({ baz: 3 }, obj); }). on( 'cycle' , function ( event ) { console .log( String (event.target)); }). on( 'complete' , function ( ) { console .log( 'Fastest is ' + this .filter( 'fastest' ).map( 'name' )); }). run({ 'async' : true });

In this case, the two are similar:

Object spread x 3,170,111 ops/sec +-1.50% (90 runs sampled) Object.assign() x 3,290,165 ops/sec +-1.86% (88 runs sampled) Fastest is Object.assign()

However, once you throw in an empty object parameter to Object.assign() , the object spread operator is consistently faster:

suite. add( 'Object spread' , function ( ) { ({ baz: 3 , ...obj }); }). add( 'Object.assign()' , function ( ) { Object .assign({}, obj, { baz: 3 }); })

Here's the output:

Object spread x 3,065,831 ops/sec +-2.12% (85 runs sampled) Object.assign() x 2,461,926 ops/sec +-1.52% (88 runs sampled) Fastest is Object spread

ESLint Configuration

By default, ESLint disallows the object rest/spread operator at the parser level. You need to set parserOptions.ecmaVersion option to at least 9 in .eslintrc.yml , otherwise you'll get a parsing error.

parserOptions: # Otherwise object spread causes 'Parsing error: Unexpected token ..' ecmaVersion: 9

ESLint added a new rule prefer-object-spread that allows you to enforce using object spread instead of Object.assign() . To enable this rule, use:

parserOptions: ecmaVersion: 9 rules: prefer-object-spread: error

Now ESLint will report an error if you use Object.assign() instead of object spread.

Use an object spread instead of `Object.assign` eg: `{ ...foo }` prefer-object-spread

Moving On