Classic issues: Deep Copy
Tags: javascript, programming
Published: 2021-03-16 20:58:09

##The Issue
I need to get a full copy of some object. Let's say I have a plain object full of primitives:
```javascript
const objExample = {
  n: 63,
  s: 'An',
  b: false,
  u: undefined,
  e: null,
};
```

###Before I start
I always like to repeat, that some sort of classic issues analysis is nothing more thant just analysis - cause if you guys need deep copy, better go to 'lodash.cloneDeep' (not even talking about cloneDeepWith), they do write code for coders and that simply means a lot.

---

###Shallow Copy
I can use Object.assign or spread operator to try to clone that:
```javascript
const assignedObject = Object.assign({}, objExample);
const spreadedObject = { ...objExample };
```
Of course, this is just 2 different syntax of the same operation, so no surprise that the result is going to be the same - our objExample will be copied to 2 different variables

But, this copying called 'Shallow Copy' - which means that it's ok working with primitives, but for structural types it will copy not a body but the reference to the structure being copied
```javascript
const objExample = {
  b: false,
  c: { a: 34 }
};
const assignedObject = Object.assign({}, objExample);

assignedObject.b = 24;
assignedObject.c.a = 45;

console.log(objExample.b, objExample.c); // false { a: 45 }
```
##How we can avoid this?
We can try with JSON serialization/deserialization technique:
```javascript
const jsonObject = JSON.parse(JSON.stringify(objExample));

jsonObject.c.a = 63;

console.log(objExample.c);
```
###But
JSON may work only with JSON-like structures, which means that you are not able to work with non-json stuff like functions, undefined etc.
```javascript
const objExample = {
  u: undefined,
  e: () => {},
  b: new Date(),
  m: new Map(),
  c: { a: 34 }
};

const jsonObject = JSON.parse(JSON.stringify(objExample));
console.log(jsonObject);
//Output: { b: '2021-03-15T08:06:01.181Z', m: {}, c: { a: 34 } }
```
So JSON doesn't work well here.
On another hand, Node.js has own package 'v8' that also has serialization methods with Buffer under the hood, but it doesn't support functions copying, so this is also not for us.
```javascript
const v8 = require('v8');

const objExample = {
  u: undefined,
  a: function() {},
  m: new Map(),
};

const v8Object = v8.deserialize(v8.serialize(objExample));
//Error: function() {} could not be cloned;
```
No choice - I need to write own solution
(If I don't know about lodash of course)

---
####Define a function
Let's start with first step - we need to define a function and say there, that for starter we will work only with arrays and objects as complex structures:

```javascript
function isObject(value) {
  return typeof value === 'object';
}

function deepCopy(value) {
  if (Array.isArray(value)) {

  } else if (value && isObject(value) {

  }
}
```

I need to add a variable to operate with in all cases and to return at the end. Also, I need to say that if my function param is a primitive or simple function - I will just rewrite this variable:
```javascript
function deepCopy(value) {
  let newVal;

  if (Array.isArray(value)) {

  } else if (value && isObject(value) {

  } else {
    newVal = value;
  }

  return newVal;
}
```
If I work with object type, I need to go through all it's keys and specify - if the key is primitive, I'm adding it to my 'newVal', otherwise I'm recursively calling my function to go through internals of nested object:
```javascript
else if (value && isObject(value)) {
    newVal = {};

    Object.keys(value).forEach(key => {
      if (isObject(value[key])) {
        newVal[key] = deepCopy(value[key]);
      } else {
        newVal[key] = value[key];
      }
    });
  }
```
And for the array structure I just need to use map method with calling deepCopy there:
```javascript
if (Array.isArray(value)) {
    newVal = value.map(item => {
      return deepCopy(item);
    });
  }
```

####Circular References
We need to develop the logic for preventing memory leak cases, when object has a field referenced with the object itself, which will create an infinite recursion and stack overflow

```javascript
const a = { b: { c: 345 } };
a.d = a;
const b = deepCopy(a);
```
For this, I need to use Map structure to set already existed keys there (WeakMap doesn't suit cause I also want to store arrays as keys)

```javascript
function deepCopy(value, hash = new Map()) {
  let newVal;

  if (hash.has(value)) {
    return hash.get(value);
  }

  if (Array.isArray(value)) {
    hash.set(value, newVal);

    newVal = value.map(item => {
      return deepCopy(item, hash);
    });
  } else if (value && isObject(value)) {
    newVal = {};

    Object.keys(value).forEach(key => {
      if (isObject(value[key])) {
        hash.set(value, newVal);
        newVal[key] = deepCopy(value[key], hash);
      } else {
        newVal[key] = value[key];
      }
    });
  } else {
    newVal = value;
  }

  return newVal;
}


const a = { b: { c: 345 } };
a.d = a;
const b = deepCopy(a);

console.log(b); //{ b: { c: 345 }, d: [Circular] }
```

###P.S.
That's of course not the perfect end for such function, cause there are a lot of corner cases to check, but if we just go to starting code of lodash's cloning function [baseClone](https://github.com/lodash/lodash/blob/master/.internal/baseClone.js)...
```javascript
/** `Object#toString` result references. */
const argsTag = '[object Arguments]'
const arrayTag = '[object Array]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const numberTag = '[object Number]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const weakMapTag = '[object WeakMap]'

const arrayBufferTag = '[object ArrayBuffer]'
const dataViewTag = '[object DataView]'
const float32Tag = '[object Float32Array]'
const float64Tag = '[object Float64Array]'
const int8Tag = '[object Int8Array]'
const int16Tag = '[object Int16Array]'
const int32Tag = '[object Int32Array]'
const uint8Tag = '[object Uint8Array]'
const uint8ClampedTag = '[object Uint8ClampedArray]'
const uint16Tag = '[object Uint16Array]'
const uint32Tag = '[object Uint32Array]'
```

So I suggest to trust that function, or if something really bothers you - it's open source, so just fork it and enjoy any changes you would like to implement there;

*Thank you for attention, hope you will like the format!*