Originally posted at michaelzanggl.com. Subscribe to my newsletter to never miss out on new content.
To understand shallow / deep copying let's step away from the keyboard for a moment and look at dimensions in General.
In fact, let's take a look at the acronym ajax
. What does it actually stand for?
Asynchronous JSON and XML
.
Wait... so the acronym ajax
is made up of two more acronyms JSON
and XML
.
In other words, the acronym ajax
has a second dimension of acronyms which makes it a multi dimensional acronym! 😱
So when we unabbreviate ajax
to Asynchronous JSON and XML
we only unabbreviate the first dimension, in other words: shallow-unabbreviating. A word that may not yet exist today, but will soon find its way into dictionaries. Notice how the second dimensions JSON
and XML
stay untouched. We are merely referencing to these other acronyms.
If we were to deep-unabbreviate ajax
, this is what we would get:
Asynchronous JavaScript Object Notation And Extensible Markup Language
Imagine, in the old days, we would have had to write
$.asynchronousJavaScriptObjectNotationAndExtensibleMarkupLanguage
Another example of a multi dimensional acronym is the JAM stack.
Shallow-unabbreviated:
JavaScript API Markup
Deep-unabbreviated:
JavaScript Application Programming Interface Markup
So let's step away from these rather unfortunately named acronyms and into the code.
const ajax = {
a: 'Asynchronous',
j: {
j: 'Java',
s: 'Script',
o: 'Object',
n: 'Notation'
},
a2: 'and',
x: {
x: 'Extensible',
m: 'Markup',
l: 'Language'
}
}
Here we have ajax
layed out in a two dimensional object.
What happens if we copy this object into another object
const obj = ajax
obj.x = null
obj.x //? null
ajax.x //? null
This won't work. obj
will be merely a reference to ajax
. Changing one will change the other respectively. That's the way objects work in JavaScript.
How about this?
const obj = Object.assign({}, ajax)
// or: const obj = {...ajax}
obj.x = null
obj.x //? null
ajax.x //? { x: 'Extensible', m: 'Markup', l: 'Language' }
Nice, we created an actual copy! Or did we...?
const obj = Object.assign({}, ajax)
obj.x.l = 'lang'
obj.x.l //? lang
ajax.x.l //? lang
Turns out Object.assign
as well as ES6 spread syntax are merely doing a shallow-copy!
So how can we possibly copy the entire object, i.e. deep-copy?
A rather hackish solution you often see is the following
const obj = JSON.parse(JSON.stringify(ajax))
While this would work in our example, it would simply remove any methods on the object. It will also not work on maps and sets.
The sad truth is, JavaScript does not provide such functionality out of the box. You can either create your very own deep copy method or make use of existing solutions.
PS. In JavaScript arrays are objects, so everything we talked about also applies to arrays.
Top comments (1)
I liked the way you transitioned the acronyms example into actual code. Well done!
If anyone is curious, here is a (non-exhaustive) benchmark list of possible Deep Copy implementations and approaches: jsben.ch/2gRXJ
There are definitely other factors to be considered, but it's interesting to see the benchmark.
And of course, everyone can add new tests to that link and generate more complete benchmarks, if one is so inclined.
Thanks for the article!