Explaining shallow / deep copying through acronyms

Published 3/27/2019

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.