Object spread vs. Object.assign

Object spread vs. Object.assign

Let’s say I have an options variable and I want to set some default value.
What’s is the benefit / drawback of these two alternatives?
Using object spread
options = {…optionsDefault, …options};

Or using Object.assign
options = Object.assign({}, optionsDefault, options);

This is the commit that made me wonder.

Solutions/Answers:

Solution 1:

This isn’t necessarily exhaustive.

Spread syntax

options = {...optionsDefault, ...options};

Advantages:

  • If authoring code for execution in environments without native support, you may be able to just compile this syntax (as opposed to using a polyfill). (With Babel, for example.)

  • Less verbose.

Disadvantages:

  • When this answer was originally written, this was a proposal, not standardized. When using proposals consider what you’d do if you write code with it now and it doesn’t get standardized or changes as it moves toward standardization. This has since been standardized in ES2018.

  • Literal, not dynamic.


Object.assign()

options = Object.assign({}, optionsDefault, options);

Advantages:

  • Standardized.

  • Dynamic. Example:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);
    

Disadvantages:

  • More verbose.
  • If authoring code for execution in environments without native support you need to polyfill.

This is the commit that made me wonder.

That’s not directly related to what you’re asking. That code wasn’t using Object.assign(), it was using user code (object-assign) that does the same thing. They appear to be compiling that code with Babel (and bundling it with Webpack), which is what I was talking about: the syntax you can just compile. They apparently preferred that to having to include object-assign as a dependency that would go into their build.

Solution 2:

For reference object rest/spread is finalised in ECMAScript 2018 as a stage 4. The proposal can be found here.

For the most part object reset and spread work the same way, the key difference is that spread defines properties, whilst Object.assign() sets them. This means Object.assign() triggers setters.

It’s worth remembering that other than this, object rest/spread 1:1 maps to Object.assign() and acts differently to array (iterable) spread. For example, when spreading an array null values are spread. However using object spread null values are silently spread to nothing.

Array (Iterable) Spread Example

const x = [1, 2, null , 3];
const y = [...x, 4, 5];

console.log(y); // [1, 2, null, 3, 4, 5];

Object Spread Example

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

This is consistent with how Object.assign() would work, both silently exclude the null value with no error.

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}

Solution 3:

I think one big difference between the spread operator and Object.assign that doesn’t seem to be mentioned in the current answers is that the spread operator will not keep the prototype intact. If you want to add properties to an object and you don’t want to change what it is an instance of, than you will have to use Object.assign. The example below should demonstrate this:

const error = new Error();
console.error instanceof Error; // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true

Solution 4:

The object spread operator (…) doesn’t work in browsers, because it isn’t part of any ES specification yet, just a proposal. The only option is to compile it with Babel (or something similar).

As you can see, it’s just syntactic sugar over Object.assign({}).

As far as I can see, these are the important differences.

  • Object.assign works in most browsers (without compiling)
  • ... for objects isn’t standardized
  • ... protects you from accidentally mutating the object
  • ... will polyfill Object.assign in browsers without it
  • ... needs less code to express the same idea

Solution 5:

As others have mentioned, at this moment of writing, Object.assign() requires a polyfill and object spread ... requires some transpiling (and perhaps a polyfill too) in order to work.

Consider this code:

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

These both produce the same output.

Here is the output from Babel, to ES5:

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

This is my understanding so far. Object.assign() is actually standardised, where as object spread ... is not yet. The only problem is browser support for the former and in future, the latter too.

Play with the code here

Hope this helps.

Solution 6:

I’d like to summarize status of the “spread object merge” ES feature, in browsers, and in the ecosystem via tools.

Spec

Browsers: in Chrome, in SF, Firefox soon (ver 60, IIUC)

  • Browser support for “spread properties” shipped in Chrome 60, including this scenario.
  • Support for this scenario does NOT work in current Firefox (59), but DOES work in my Firefox Developer Edition. So I believe it will ship in Firefox 60.
  • Safari: not tested, but Kangax says it works in Desktop Safari 11.1, but not SF 11
  • iOS Safari: not teseted, but Kangax says it works in iOS 11.3, but not in iOS 11
  • not in Edge yet

Tools: Node 8.7, TS 2.1

Links

Code Sample (doubles as compatibility test)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

Again: At time of writing this sample works without transpilation in Chrome (60+), Firefox Developer Edition (preview of Firefox 60), and Node (8.7+).

Why Answer?

I’m writing this 2.5 years after the original question. But I had the very same question, and this is where Google sent me. I am a slave to SO’s mission to improve the long tail.

Since this is an expansion of “array spread” syntax I found it very hard to google, and difficult to find in compatibility tables. The closest I could find is Kangax “property spread”, but that test doesn’t have two spreads in the same expression (not a merge). Also, the name in the proposals/drafts/browser status pages all use “property spread”, but it looks to me like that was a “first principal” the community arrived at after the proposals to use spread syntax for “object merge”. (Which might explain why it is so hard to google.) So I document my finding here so others can view, update, and compile links about this specific feature. I hope it catches on. Please help spread the news of it landing in the spec and in browsers.

Lastly, I would have added this info as a comment, but I couldn’t edit them without breaking the authors’ original intent. Specifically, I can’t edit @ChillyPenguin’s comment without it losing his intent to correct @RichardSchulte. But years later Richard turned out to be right (in my opinion). So I write this answer instead, hoping it will gain traction on the old answers eventually (might take years, but that’s what the long tail effect is all about, after all).