DEV Community

loading...

Explaining shallow / deep copying through acronyms

michi profile image Michael Z Originally published at michaelzanggl.com Updated on ・2 min read

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'
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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' }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (1)

pic
Editor guide
Collapse
luanf profile image
Luan Ferreira

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!