599

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.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Olivier Tassinari
  • 8,238
  • 4
  • 23
  • 23
  • 10
    Define "*best*" (carefully, don't end up with an opinion based question :-) – Amit Oct 03 '15 at 17:44
  • 2
    Could also depend how you want to support it if running in environments without native support. Syntax you might be able to just compile. An object or method you might need to polyfill. – JMM Oct 03 '15 at 17:47
  • 1
    As of now, TypeScript 1.6 does not support spread. – Colliot Nov 08 '15 at 18:43
  • 17
    apart from the compatibility issues, Object.assign can mutate the original object which is useful. spread cannot. – pstanton Jan 24 '18 at 02:59
  • 6
    To clarify @pstanton's comment - object.assign can modify an existing *target* object (overwrite properties from source, while leaving other properties intact); it doesn't touch the *source* object. I first read his "original object" as "source object", so writing this note for anyone else who similarly misreads it. :) – ToolmakerSteve Oct 10 '19 at 15:54
  • So, might also be good to point out that the Object.assign() (and likewise for spread: const copied = { ...original };) does a "shallow copy" meaning only the "top" level object is copied, the rest (children) are referenced and thus changing the original likewise affects, and changes, the value of the copied object! – Anders Jun 11 '20 at 05:40

16 Answers16

415

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.

JMM
  • 26,019
  • 3
  • 50
  • 55
  • 18
    It might be worth noting that object rest spread has moved to stage 3 so will likely be standardized in the future https://twitter.com/sebmarkbage/status/781564713750573056 – williaster Oct 26 '16 at 18:28
  • 17
    @JMM I'm not sure I see "More verbose." as a disadvantage. – DiverseAndRemote.com Apr 07 '17 at 00:27
  • 7
    @Omar well I guess you just proved that it's an opinion :) If someone doesn't recognize that advantage then yeah, all else being equal they can just use `Object.assign()`. Or you could manually iterate over an array of objects and their own props manually assigning them to a target and make it even more verbose :P – JMM Apr 07 '17 at 00:46
  • 1
    Just went to stage 4, which means it's finished. Looks like it's going in the 2018 spec? – rtf Feb 27 '18 at 19:04
  • 1
    Looks like it's in the [2018 draft spec](https://tc39.github.io/ecma262/2018/). See for example `AssignmentRestProperty`, `PropertyDefinition : ... AssignmentExpression`. – JMM Feb 27 '18 at 20:23
  • 7
    as @JMM mentionned, it's now in the ES2018 spec https://node.green/#ES2018-features-object-rest-spread-properties – Sebastien H. Mar 27 '18 at 17:55
  • @OmarJackman Most web/JS programmers care a lot about concise syntax b/c our code has to go over the wire for every user. Every byte counts. Hence efforts like https://github.com/developit/unistore and preact. This isn't just opinion, it's dedication to performance out of respect for our users' time. – yzorg Mar 30 '18 at 13:01
  • 3
    "Every byte counts" Thats what minimizers/uglify is for @yzorg – DiverseAndRemote.com Mar 31 '18 at 17:53
  • why use `[{}].concat(sources)`, since you just use one array and the concat's method is used to join two or more arrays? – Webwoman May 14 '19 at 06:07
  • 1
    Hi @Webwoman, the 2 arrays in this case are the array literal: `[{}]` and `sources`. Note that the example could be written other ways including: `Object.assign({}, ...sources)`. I may have not used that version to avoid any potential for confusion about what spread was being used, but if `Object.assign` is available, spreading of iterables (e.g. arrays) likely is too. – JMM May 14 '19 at 11:55
  • @JMM -I'm not sure I understand your use of the term "Dynamic" here. Are you trying to demonstrate the ability to alter an existing object? If so, the example doesn't show that well, as you start with `{}`. A better example would be to make an object with properties "b" and "d", store that in a variable, then demonstrate that you can change that object to have properties "a", "b", "c", and "d", and the variable refers to that changed object (without needing re-assignment to the variable). – ToolmakerSteve Oct 10 '19 at 16:04
  • @ToolmakerSteve What I mean is that using the syntax you have to literally write a reference to each source object, e.g. `{...a, ...b, ...c, /* etc. */}`. Whereas with `Object.assign` you can just spread an array of source objects as the source arguments: `Object.assign({}, ...anyNumberOfSources)`. Does that make sense? – JMM Oct 10 '19 at 18:14
  • @JMM [Object Spread](https://shorturl.at/oqUZ3) vs [Object.assign](https://shorturl.at/sGKQW) Babel seems to polyfill Object Spread even more than assign in environments without native support – Tom M Mar 04 '20 at 11:53
  • can any user help clarify the advantage of the spread syntax, and why same is not applicable to Object.assign? – Eazicoding Jul 30 '21 at 17:35
315

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 assign 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];
const z = null;

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

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}
Dennis
  • 3,962
  • 7
  • 26
  • 44
tomhughes
  • 4,597
  • 2
  • 24
  • 33
  • yes, better answer, node since v5 support spread, chrome and firefox modern version too, don't work with IE same for a normal web, I disable browse my webs with IE – stackdave Apr 09 '18 at 15:33
  • 14
    This should be the answer ... it's the future now. – Zachary Abresch May 09 '18 at 14:56
  • 3
    This is the right answer. The main different is `Object.assign` will use setters. `Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}` vs. `{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}` – David Boho May 29 '18 at 14:36
  • 2
    "The future is NOW!" -[George Allen](https://images-na.ssl-images-amazon.com/images/I/51QsOyT3SWL._SX307_BO1,204,203,200_.jpg) Tomorrow is too late. – ruffin Jun 07 '18 at 21:07
  • @David Boho, your difference is a little wrong. In `Object.assign` case you set prop `a` in the object `{set a(v){this.b=v}, b:2}` In spread case you convert `{set a(v){this.b=v}, b:2}` into {a: undefined, b: 2} object with spread operator and after this set `a` property into 4. You have to use `Object.assign({}, {set a(v){this.b=v}, b:2}, {a:4})`. This is correct comparison and both results are equal now. – Alexander Jan 13 '19 at 05:08
  • 4
    The demonstration of null being handled differently is "apples and oranges" - not a meaningful comparison. In the array case, null is an element of the array. In the spread case, null is the entire object. The correct comparison would be for x to have a *null property*: `const x = {c: null};`. In which case, AFAIK, we would see behavior just like the array: `//{a: 1, b: 2, c: null}`. – ToolmakerSteve Oct 10 '19 at 16:08
  • @ToolmakerSteve Correct, although the illustrated case should be attempting to spread a null value (a non-iterable) into the array to demonstrate the differences. I have updated the examples. – tomhughes Oct 15 '19 at 13:59
  • 3
    Thanks - with that change, I see the point you are making. object spread doesn't complain about a null object, it simply skips it, but array spread gives TypeError if attempt to spread a null object. – ToolmakerSteve Oct 15 '19 at 14:44
74

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 copy the the source object’s prototype to the target object. If you want to add properties to an object and you don't want to change what instance it is of, then you will have to use Object.assign.

Edit: I've actually realised that my example is misleading. The spread operator desugars to Object.assign with the first parameter set to an empty object. In my code example below, I put error as the first parameter of the Object.assign call so the two are not equivalent. The first parameter of Object.assign is actually modified and then returned which is why it retains its prototype. I have added another example below:

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

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

// What the spread operator desugars into
const errorExtendedUsingImmutableObjectAssign = Object.assign({}, error, {
    someValue: true
});
errorExtendedUsingImmutableObjectAssign instanceof Error; // false

// The error object is modified and returned here so it keeps its prototypes
const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true

See also: https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md

Sean Dawson
  • 5,587
  • 2
  • 27
  • 34
  • *"will not keep the prototype intact"* - it surely will, as it does not modify anything. In your example, `errorExtendedUsingAssign === error`, but `errorExtendedUsingSpread` is a new object (and the prototype wasn't copied). – maaartinus Oct 03 '19 at 23:46
  • 2
    @maaartinus You’re right, I probably worded that badly. I meant that the prototype is not on the copied object. I might edit that to be clearer. – Sean Dawson Oct 04 '19 at 21:15
  • Is the following a way to "shallow clone" an object with its class? `let target = Object.create(source); Object.assign(target, source);` – ToolmakerSteve Oct 10 '19 at 16:17
  • @ToolmakerSteve Yes it will copy all the object's "own properties" across which will effectively be a shallow clone. See: https://stackoverflow.com/questions/33692912/using-object-assign-and-object-create-for-inheritance – Sean Dawson Oct 11 '19 at 00:02
  • Is there really a difference in handling prototypes between `Object.assign()` and the spread operator? AFAIK both ignore properties from the source prototypes. – wortwart Aug 25 '20 at 10:27
  • 1
    @wortwart I suppose the difference is actually that the spread operator does not affect the source object (`error` in this case) where as the `Object.assign` will modify the source object and return it. The spread operator is really just sugar for Object.assign with the first parameter set to empty object (https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md) – Sean Dawson Aug 26 '20 at 01:20
28

There's a huge difference between the two, with very serious consequences. The most upvoted questions do not even touch this, and the information about object spread being a proposal is not relevant in 2022 anymore.

The difference is that Object.assign changes the object in-place, while the spread operator (...) creates a brand new object, and this will break object reference equality.

First, let's see the effect, and then I'll give a real-world example of how important it is to understand this fundamental difference.

First, let's use Object.assign:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });

// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true

Now the same exercise with the spread operator:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using the spread operator;
parentObject.childObject = {
  ...parentObject.childObject,
  foo: 'bar',
}

// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false

It's easy to see what is going on, because on the parentObject.childObject = {...} we are cleary assigning the value of the childObject key in parentObject to a brand new object literal, and the fact it's being composed by the old childObject content's is irrelevant. It's a new object.

And if you assume this is irrelevant in practice, let me show a real world scenario of how important it is to understand this.

In a very large Vue.js application, we started noticing a lot of sluggishness when typing the name of the customer in an input field.

After a lot of debugging, we found out that each char typed in that input triggered a hole bunch of computed properties to re-evaluate.

This wasn't anticipated, since the customer's name wasn't used at all in those computeds functions. Only other customer data (like age, sex) was being used. What was goin on? Why was vue re-evaluating all those computed functions when the customer's name changed?

Well, we had a Vuex store that did this:

mutations: {
  setCustomer(state, payload) {
    // payload being { name: 'Bob' }
    state.customer = { ...state.customer, ...payload };
  }

And our computed were like this:

veryExpensiveComputed() {
   const customerAge = this.$store.state.customer.age;
}

So, voilá! When the customer name changed, the Vuex mutation was actually changing it to a new object entirely; and since the computed relied on that object to get the customer age, Vue counted on that very specific object instance as a dependency, and when it was changed to a new object (failing the === object equality test), Vue decided it was time to re-run the computed function.

The fix? Use Object.assign to not discard the previous object, but to change it in place ...

mutations: {
  setCustomer(state, payload) {
    // payload being same as above: { name: 'Bob' }
    Object.assign(state.customer, payload);
  }

BTW, if you are in Vue2, you shouldn't use Object.assign because Vue 2 can't track those object changes directly, but the same logic applies, just using Vue.set instead of Object.assign:

mutations: {
  setCustomer(state, payload) {
    Object.keys(payload).forEach(key => {
      Vue.set(state.customer, key, payload[key])
    })
  }
sandre89
  • 5,218
  • 2
  • 43
  • 64
  • 2
    This is very much an important distinction, but the question is asking about the differences between `Object.assign({}, obj1, obj2)` and `{...obj1, ...obj2}`. Having `{}` as the first argument mitigates any concerns about mutation from `Object.assign` – Zach Lysobey Aug 10 '22 at 22:25
17

NOTE: Spread is NOT just syntactic sugar around Object.assign. They operate much differently behind the scenes.

Object.assign applies setters to a new object, Spread does not. In addition, the object must be iterable.

Copy Use this if you need the value of the object as it is at this moment, and you don't want that value to reflect any changes made by other owners of the object.

Use it for creating a shallow copy of the object good practice to always set immutable properties to copy - because mutable versions can be passed into immutable properties, copy will ensure that you'll always be dealing with an immutable object

Assign Assign is somewhat the opposite to copy. Assign will generate a setter which assigns the value to the instance variable directly, rather than copying or retaining it. When calling the getter of an assign property, it returns a reference to the actual data.

Charles Owen
  • 2,403
  • 1
  • 14
  • 25
  • 6
    Why does it say "Copy"? What are these bold headings. I feel like I missed some context when I read this... – ADJenks May 15 '19 at 17:40
  • 1
    I think you meant "Spread" when you said "Copy", if yes, I understood your answer :) – Vivek Singh Jun 29 '20 at 16:39
  • 4
    It's crazy that only this answer addresses the fact that there's a loss of reference equality when using the spread operator, and it's the second least upvoted answer here. – Slbox Sep 21 '20 at 23:47
14

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).

yzorg
  • 4,224
  • 3
  • 39
  • 57
  • 7
    your "why answer" section isn't probably needed – LocustHorde Sep 28 '18 at 08:13
  • @LocustHorde Maybe I could move the 2nd paragraph (why this topic is so difficult to google) to it's own section. Then the rest might fit into a comment. – yzorg Sep 28 '18 at 17:27
  • 1
    Note that Safari will have the following for `console.log(z)`: `{b: 2, c: 3, d: 4, a: 5}` (order is different) – Rogier Slag Jun 23 '20 at 13:40
11

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.

L.P.
  • 406
  • 5
  • 17
Michael Giovanni Pumo
  • 14,338
  • 18
  • 91
  • 140
  • 1
    Thank you! Your code sample makes the decision really easy for my context. The transpiler (babel or typescript) makes the spread operator more compatible to browsers by including a pollyfill in the inline code. Just for interest the TypeScript transpiled version is virtually the same as Babel: https://www.typescriptlang.org/play/#src=const%20objSpread%20%3D%20%7B%20message%3A%20'Hello%20you!'%20%7D%3B%0D%0Aconst%20newObjSpread%20%3D%20%7B...objSpread%2C%20dev%3A%20true%20%7D%3B%0D%0Aconsole.log(newObjSpread)%3B – Mark Whitfeld Feb 21 '17 at 06:56
  • 2
    hmm... wouldn't your two cases *not* produce the same results? in the first case you're copying properties from one object to another, and in the other you're creating a new object. Object.assign returns the target, so in your first case objAss and newObjAss are the same. – Kevin B Aug 17 '17 at 16:17
  • Adding a new first parameter of `{}` should fix the inconsistency. – Kevin B Aug 17 '17 at 16:25
7

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
Karthick Kumar
  • 1,217
  • 1
  • 25
  • 38
  • 42
    It's *not* syntactic sugar for `Object.assign`, as the spread operator will always give you a new object. – MaxArt Dec 16 '16 at 22:12
  • 4
    Actually, i'm surprised that other people aren't emphasizing the mutability difference more. Think of all of the developer hours lost debugging accidental mutations with Object.assign – deepelement Feb 13 '19 at 17:35
  • This is now supported in most modern browsers (as with other ES6): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax – akmalmzamri Apr 11 '20 at 09:04
4

Too many wrong answers...

I did a search and found a lot of misinformation on this.

Summary

  1. Neither ...spread nor Object.assign is faster. It depends.
  2. Object.assign is almost always faster if side-effects/object mutation is not a consideration.
  3. Performance aside, there is generally no up/downside to using either, until you reach edge cases, such copying objects containing getters/setters, or read-only properties. Read more here.

Performance

Whether Object.assign or ...spread is faster, it depends on what you are trying to combine, and the runtime you are using (implementations and optimisations happen from time to time). For small objects, it does not matter.

For bigger objects, Object.assign is usually better. Especially if you do not need to care about side-effects, the speed gains come from saving time by just adding properties to the first object, rather than copying from two and creating a brand new one. See:

async function retrieveAndCombine() {
    const bigPayload = await retrieveData()
    const smallPayload = await retrieveData2()
    
    // only the properties of smallPayload is iterated through
    // whereas bigPayload is mutated.
    return Object.assign(bigPayload, smallPayload)
}

If side-effects is a concern

In cases where side-effects matters, such as if an object to be combined with another is passed in as an argument.

In the example below, bigPayload will be mutated, which is a bad idea if other functions/objects outside of retrieveAndCombine depends on bigPayload. In this case, you can swap the arguments passed to Object.assign, or just use {} as the first argument to create a new object:

async function retrieveAndCombine(bigPayload) {
    const smallPayload = await retrieveData2()

    // this is slightly more efficient
    return Object.assign(smallPayload, bigPayload)

    // this is still okay assuming `smallPayload` only has a few properties
    return Object.assign({}, smallPayload, bigPayload)
}

Test

See if for yourself, try the code snippet below. Note: It takes awhile to run.

const rand = () => (Math.random() + 1).toString(36).substring(7)

function combineBigObjects() {
console.log('Please wait...creating the test objects...')
const obj = {}
const obj2 = {}

for (let i = 0; i < 100000; i++) {
    const key = rand()
    obj[rand()] = {
        [rand()]: rand(),
        [rand()]: rand(),
    }
    obj2[rand()] = {
        [rand()]: rand(),
        [rand()]: rand(),
    }
}
console.log('Combine big objects using spread:')
console.time()
const result1 = {
    ...obj,
    ...obj2,
}
console.timeEnd()

console.log('Combine big objects using assign:')
console.time()
Object.assign({}, obj, obj2)
console.timeEnd()

console.log('Combine big objects using assign, but mutates first obj:')
console.time()
Object.assign(obj, obj2)
console.timeEnd()
}

combineBigObjects()

function combineSmallObjects() {
const firstObject = () => ({ [rand()]: rand() })
const secondObject = () => ({ [rand()]: rand() })
console.log('Combine small objects using spread:')
console.time()
for (let i = 0; i < 500000; i++) {
    const finalObject = {
        ...firstObject(),
        ...secondObject(),
    }
}
console.timeEnd()

console.log('Combine small objects using assign:')
console.time()
for (let i = 0; i < 500000; i++) {
    const finalObject = Object.assign({}, firstObject(), secondObject())
}
console.timeEnd()

console.log('Combine small objects using assign, but mutates first obj:')
console.time()
for (let i = 0; i < 500000; i++) {
    const finalObject = Object.assign(firstObject(), secondObject())
}
console.timeEnd()
}

combineSmallObjects()
Calvintwr
  • 8,308
  • 5
  • 28
  • 42
2

Other answers are old, could not get a good answer.
Below example is for object literals, helps how both can complement each other, and how it cannot complement each other (therefore difference):

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

Several more small examples here, also for array & object:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
  • 25,399
  • 9
  • 157
  • 140
2

The ways to (1) create shallow copies of objects and (2) merge multiple objects into a single object have evolved a lot between 2014 and 2018.

The approaches outlined below became available and widely used at different times. This answer provides some historical perspective and is not exhaustive.

  • Without any help from libraries or modern syntax, you would use for-in loops, e.g.

    var mergedOptions = {}
    for (var key in defaultOptions) {
      mergedOptions[key] = defaultOptions[key]
    }
    for (var key in options) {
      mergedOptions[key] = options[key]
    }
    
    options = mergedOptions
    

2006

  • jQuery 1.0 has jQuery.extend():

    options = $.extend({}, defaultOptions, options)
    

2010

2014

2015

  • Object.assign is supported by Chrome (45), Firefox (34) and Node.js (4). Polyfill is required for older runtimes though.

    options = Object.assign({}, defaultOptions, options)
    
  • The Object Rest/Spread Properties proposal reaches stage 2.

2016

  • The Object Rest/Spread Properties syntax did not get included in ES2016, but proposal reaches stage 3.

2017

  • The Object Rest/Spread Properties syntax did not get included in ES2017, but is usable in Chrome (60), Firefox (55), and Node.js (8.3). Some transpilation is needed for older runtimes though.

    options = { ...defaultOptions, ...options }
    

2018

  • The Object Rest/Spread Properties proposal reaches stage 4 and the syntax is included in ES2018 standard.
Thai
  • 10,746
  • 2
  • 45
  • 57
2

Object.assign is necessary when the target object is a constant and you want to set multiple properties at once.

For example:

const target = { data: "Test", loading: true }

Now, suppose you need to mutate the target with all properties from a source:

const source = { data: null, loading: false, ...etc }

Object.assign(target, source) // Now target is updated
target = { ...target, ...source) // Error: cant assign to constant

Keep in mind that you are mutating the target obj, so whenever possible use Object.assign with empty target or spread to assign to a new obj.

1

This is now part of ES6, thus is standardized, and is also documented on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

It's very convenient to use and makes a lot of sense alongside object destructuring.

The one remaining advantage listed above is the dynamic capabilities of Object.assign(), however this is as easy as spreading the array inside of a literal object. In the compiled babel output it uses exactly what is demonstrated with Object.assign()

So the correct answer would be to use object spread since it is now standardized, widely used (see react, redux, etc), is easy to use, and has all the features of Object.assign()

Rikki Schulte
  • 186
  • 2
  • 7
  • 3
    No, it's not part of ES6. The link that you've supplied refers to the spread operator's use on _arrays_ only. Use of the spread operator on _objects_ is currently a Stage 2 (i.e. Draft) proposal, as explained in JMM's answer. – ChillyPenguin Sep 15 '16 at 02:25
  • 2
    I don't think I've ever seen an answer so entirely based on false information. Even a year later, that is not part if the ES spec and not supported in most environments. – 3ocene Jul 11 '17 at 23:33
  • 1
    Chilly and 3o turned out to be wrong, Richard right. Browser support and tool support are all landing, but it took 1.5 years after Richard's answer. See my new answer for summary of support as of Mar 2018. – yzorg Mar 30 '18 at 15:16
  • 1
    @yzorg thank you for defending, me, however my answer was factually wrong for 2 years, so they were both correct at the time of commenting. good on you for providing an updated answer! ChillyPenguin you are correct. 3ocene agreed, this is probably the most false answer I've seen as well haha! it landed in ES2018, a year after your comment. too bad i can't downvote my own answers. I would delete it, but technically it's still valid if you read it now. to be honest? I still like to use Object.assign() sometimes :shrug: – Rikki Schulte Feb 06 '21 at 01:42
  • @RikkiSchulte At the time I was frustrated most info on this page was outdated "historical" info that gave the impression the feature was still not available. But, then again, I still have caveats in my answer that are way out of date and should be removed, or the whole thing updated. – yzorg Feb 08 '21 at 15:31
0

I'd like to add this simple example when you have to use Object.assign.

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

It can be not clear when you use JavaScript. But with TypeScript it is easier if you want to create instance of some class

const spread: SomeClass = {...new SomeClass()} // Error
zemil
  • 3,235
  • 2
  • 24
  • 33
-2

The spread operator spread the Array into the separate arguments of a function.

let iterableObjB = [1,2,3,4]
function (...iterableObjB)  //turned into
function (1,2,3,4)
Shaik Md N Rasool
  • 484
  • 1
  • 5
  • 13
  • 1
    This question is about the spread syntax as applied to objects vs the use of `Object.assign`, nothing to do with arrays or function calls. – Heretic Monkey Jun 21 '21 at 17:55
-13

We’ll create a function called identity that just returns whatever parameter we give it.

identity = (arg) => arg

And a simple array.

arr = [1, 2, 3]

If you call identity with arr, we know what’ll happen

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
b devid
  • 1
  • 1