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.
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.
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.
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.
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:
“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.
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:
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.