avatarDr. Derek Austin 🥳

Summary

The context discusses various methods of copying objects and arrays in JavaScript, with a focus on deep copying for nested objects.

Abstract

The context begins with an explanation of shallow copying, which creates new references to primitive values within an object but does not copy nested objects. Deep copying is then introduced as a solution for nested objects, with four methods of shallow copying and five methods of deep copying discussed. The methods of shallow copying include the spread operator, .slice(), .assign(), and Array.from(). The methods of deep copying include using libraries such as lodash and Ramda, custom functions, JSON.parse() / JSON.stringify(), and the rfdc library. The context also discusses the performance of these copying methods and concludes with further reading recommendations.

Bullet points

  • Shallow copying creates new references to primitive values within an object but does not copy nested objects.
  • Deep copying is necessary for nested objects.
  • Four methods of shallow copying are discussed: spread operator, .slice(), .assign(), and Array.from().
  • Five methods of deep copying are discussed: lodash, Ramda, custom functions, JSON.parse() / JSON.stringify(), and rfdc.
  • The performance of these copying methods is discussed, with rfdc being recommended for its speed.
  • The context concludes with further reading recommendations.

How to Deep Copy Objects and Arrays in JavaScript

The usual methods of copying an object or array only make a shallow copy, so deeply-nested references are a problem. You need a deep copy if a JavaScript object contains other objects.

Photo by João Silas on Unsplash

What is a shallow copy?

Making a shallow copy of an array or object means creating new references to the primitive values inside the object, copying them.

That means that changes to the original array will not affect the copied array, which is what would happen if only the reference to the array had been copied (such as would occur with the assignment operator =).

A shallow copy refers to the fact that only one level is copied, and that will work fine for an array or object containing only primitive values.

For objects and arrays containing other objects or arrays, copying these objects requires a deep copy. Otherwise, changes made to the nested references will change the data nested in the original object or array.

In this article, I describe 4 methods of making a shallow copy and then 5 methods of making a deep copy in JavaScript.

Photo by Jakob Owens on Unsplash

Shallow copy using …

1. The spread operator () is a convenient way to make a shallow copy of an array or object —when there is no nesting, it works great.

As shown above, the spread operator is useful for creating new instances of arrays that do not behave unexpectedly due to old references. The spread operator is thus useful for adding to an array in React State.

Photo by Donald Giannatti on Unsplash

Shallow copy using .slice()

2.For arrays specifically, using the built-in .slice() method works the same as the spread operator — creating a shallow copy of one level:

Photo by Antonio Garcia on Unsplash

Shallow copy using .assign()

3.The same type of shallow copy would be created using Object.assign(), which can be used with any object or array:

Photo by Paweł Czerwiński on Unsplash

Shallow copy arrays using Array.from()

4.Another method to copy a JavaScript array is using Array.from(), which will also make a shallow copy, as shown in this example:

If an object or array contains other objects or arrays, shallow copies will work unexpectedly, because nested objects are not actually cloned.

For deeply-nested objects, a deep copy will be needed. I explain why below.

Photo by brabus biturbo on Unsplash

Watch out for the deeply-nested Gotcha!

On the other hand, when JavaScript objects including arrays are deeply nested, the spread operator only copies the first level with a new reference, but the deeper values are still linked together.

To solve this problem requires creating a deep copy, as opposed to a shallow copy. Deep copies can be made using lodash, rfdc, or the R.clone() method from the Ramda functional programming library. I explore deep copies next.

Photo by Landon Martin on Unsplash

What is a deep copy?

For objects and arrays containing other objects or arrays, copying these objects requires a deep copy. Otherwise, changes made to the nested references will change the data nested in the original object or array.

This is compared to a shallow copy, which works fine for an object or array containing only primitive values, but will fail for any object or array that has nested references to other objects or arrays.

Understanding the difference between >== and === can help visually see the difference between shallow and deep copy, as the strict equality operator (===) shows that the nested references are the same:

I will cover 5 methods of making a deep copy (or deep clone): lodash, Ramda, a custom function, JSON.parse() / JSON.stringify(), and rfdc.

Photo by mya thet khine on Unsplash

Deep copy with lodash

1.The library lodash is the most common way JavaScript developers make a deep copy. It is surprisingly easy to use:

Lodash’s name comes from the library being referenced as an underscore (_), a “low dash” or lodash for short.

Photo by Annie Spratt on Unsplash

Deep copy with Ramda

2.The functional programming library Ramda includes the R.clone() method, which makes a deep copy of an object or array.

Note that R.clone() from Ramda is equivalent to _.cloneDeep() for lodash, as Ramda does not have a shallow copy helper method.

Photo by Roi Dimor on Unsplash

Deep copy with custom function

3. It is pretty easy to write a recursive JavaScript function that will make a deep copy of nested objects or arrays. Here is an example:

Note that I also need to check for null since the typeof null is “object.”

Photo by Scott Webb on Unsplash

Deep copy with JSON.parse/stringify

4. If your data fits the specifications (see below), then JSON.stringify followed by JSON.parse will deep copy your object.

“If you do not use Dates, functions, undefined, Infinity, [NaN], RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types within your object, a very simple one liner to deep clone an object is: JSON.parse(JSON.stringify(object))” — Dan Dascalescu in his StackOverflow answer

To demonstrate some reasons why this method is not generally recommended, here is an example of creating a deep copy using JSON.parse(JSON.stringify(object)):

A custom function or the libraries mentioned can make a deep copy without needing to worry about the type of the contents, though circular references will trip all of them up.

Next I discuss a blazing-fast library called rfdc that can handle circular references while being as fast as a custom deep copy function.

Photo by Scott Webb on Unsplash

Really fast deep copy? Think rfdc

5. For the best performance, the library rfdc (Really Fast Deep Clone) will deep copy about 400% faster than lodash’s _.cloneDeep:

“rdfc clones all JSON types: •Object •Array •Number •String •null

With additional support for: •Date (copied) •undefined (copied) •Function (referenced) •AsyncFunction (referenced) •GeneratorFunction (referenced) •arguments (copied to a normal object)

All other types have output values that match the output of JSON.parse(JSON.stringify(o)).” —rfdc Documentation

Using rfdc is pretty straight-forward, much like the other libraries:

The rfdc library supports all types and also supports circular references with an optional flag that decreases performance by about 25%.

Circular references will break the other deep copy algorithms discussed.

Such a library would be useful if you are dealing with a large, complex object such as one loaded from JSON files from 3MB-15MB in size.

Here are the benchmarks, showing rfdc is about 400% faster when dealing with such large objects:

benchLodashCloneDeep*100: 1461.134ms benchRfdc*100: 323.899ms benchRfdcCircles*100: 384.561ms rfdc Documentation

  • The library rfdc (Really Fast Deep Clone) is on GitHub and npm:
Photo by Scott Webb on Unsplash

Performance of JavaScript Copy Algorithms

Of the various copy algorithms, the shallow copies are the fastest, followed by deep copies using a custom function or rfdc:

“Deep copy by performance: Ranked from best to worst

Reassignment “=” (string arrays, number arrays — only)

Slice (string arrays, number arrays — only)

Concatenation (string arrays, number arrays — only)

Custom function: for-loop or recursive copy

[Author’s note: rfdc would be here, as fast as a custom function]

jQuery’s $.extend

JSON.parse (string arrays, number arrays, object arrays — only)

Underscore.js’s _.clone (string arrays, number arrays — only)

Lo-Dash’s _.cloneDeep” — Tim Montague in his StackOverflow answer

Using JSON.parse/JSON.stringify creates issues around data types, so rfdc is recommended — unless you want to write a custom function.

Photo by Keila Hötzel on Unsplash

Conclusion: How to Deep Copy in JavaScript

It is actually pretty easy to avoid needing to deep copy in JavaScript — if you can just never nest objects and arrays inside each other.

Because in that case — where there is no nesting and the objects and arrays only contain primitive values — making a shallow copy with the spread operator (), .slice(), and .assign() all work great.

But, in the real world, where objects have arrays inside them, or vice versa, then a deep copy will need to be used. I recommend rfdc for deep clones.

(Note that some may also suggest using JSON.stringify() followed by JSON.parse(), but that is not a reliable way to make a deep copy.)

Now get out there and deep copy some nested objects!

Photo by Kristina Evstifeeva on Unsplash

Further reading

  • Alligator.io has a great article on deep cloning using lodash:
  • Peter Tasker’s article on dev.to generated many comments, including an explanation of when JSON.parse(JSON.stringify(obj)) fails:
Photo by 青 晨 on Unsplash

Dr. Derek Austin is the author of Career Programming: How You Can Become a Successful 6-Figure Programmer in 6 Months, now available on Amazon.

JavaScript
Programming
Technology
Software Development
Web Development
Recommended from ReadMedium