Attack of the Clones: When to use JavaScript Object.assign vs spread operator

Traditionally making a clone of an object in JavaScript has not been simple, nor straightforward. You will run into the problem of erroneously picking up attributes from the object’s prototype that should be left in the prototype and not copied to the new instance.

However, the ES6 updates added a new JavaScript feature Object.assign(), which simplifies the process.

In this post, I will outline the following information about Object.assign()

  • When to use
  • How to use
  • Limitations
  • Alternatives
  • Object.assign() in Redux

When to use Object.assign()

We have the following Object, and want to clone it:

How to use

To clone, simply type:

const shallowClone = Object.assign({}, obj);

However, PROCEED WITH CAUTION.

This is only a “Shallow” clone. Let me demonstrate what I mean.

Limitations

Let’s reassign the value of “c” to the integer 3004, on our new shallowClone object. Should be simple enough:

shallowClone.a.b.c = 3004;

Now, lets console.log the new value of c on shallowObject, and the value of c on our original object, obj.

console.log("shallowClone", shallowClone.a.b.c);
console.log("Original Object", obj.a.b.c);
// shallowClone 3004
// Original Object 3004

Wait..

Changing the value of c on shallowObject, also modified the value of c on our original object! This is what is meant by a shallow copy. Rather than making a complete clone, this “shallow” clone merely provides reference to values beyond one level deep — it is *NOT* a true clone.

Alternatives

So by now you are probably wondering, “Chris, what are some possibly alternatives to Object.assign()? The syntax is absolutely phenomenal, but sometimes I need a deeper clone than simply one level deep.”

Don’t worry. I got you.

There are a few possible solutions to creating a deep clone in JavaScript:

  1. JSON.stringify
  2. Complicated solution with recursion — more performant, especially relevant for large objects
  3. jQuery extend — not recommended for performance issues

In this post, I will be demonstrating how using JSON.stringify solves our problem. Two other options are outlined in this excellent Stack Overflow post.

To quickly summarize, the other possibilities are a more complicated solution involving recursion, which I would recommend for larger objects, and to use JQuery which I would not recommend because it is less performant then the solution I am about to propose:

Above you can see our new object (same as our old object, with a new name). We then create a deep clone by converting the object to a string, and then parsing it back to JSON. Let’s test if it works by assigning a new value to c on the jasonClone:

jasonClone.a.b.c = 40000;

Now we can check impact using console.log. Drumroll please..

console.log("jasonClone", jasonClone.a.b.c);
console.log("Original Object", newObj.a.b.c);
// jasonClone 40000
// Original Object 1

Voila! As you can see we have sucessfully deep cloned newObj. The c value on jasonClone updates while the c value on newObj remains the same

Object.assign() in Redux

In this wrap up, I wanted to touch on my most common use case of Object.assign(). One of the core tenets of Redux is to never mutate state, you’ll often find yourself using Object.assign() to create copies of objects with new or updated values. Especially as you are writing your reducer functions.

While this is an effective approach, note that utilizing the new object spread syntax to copy enumerable properties from one object to another is more succinct and will result in more readable code, but still requires a babel plugin (babel-plugin-transform-object-rest-spread) as of the writing of this blog post (interestingly, this plugin is a polyfill for Object.assign()).

Sources:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

https://redux.js.org/recipes/usingobjectspreadoperator


Originally published at gist.github.com.