Deep Cloning Objects
h
Coder100 (12440)

Deep Cloning Objects

Why should one even be interested in this one may ask? Because it will most definitely affect you in many aspects. This is one of the most annoying properties of JS.

const myArr = [1, 2, 3];
let copy = myArr;

copy[3] = 4;
console.log(copy, myArr); // [1, 2, 3, 4], [1, 2, 3, 4]

?!?! myArr is constant!! wtf!!

Reason

I am sure you will find a real world application that concerns two arrays like in this example. Before we discuss a solution, let's first talk about why this is.

When you pass in numbers to be copied:

const myNum = 3;
let copy = myNum;

copy++;
console.log(copy, myNum); // 4, 3

And even strings (remember, in JS, strings are not array pointers like in C and C++):

const myNum = "123";
let copy = myNum;

copy += "4";
console.log(copy, myNum); // 1234, 123

it copies by value. This is very much what is expected. In the backend, what happens is that the value is passed, and everyone is happy! However, objects and arrays in the backend are just pointers to memory addresses, and you are being given the pointer! Not what you want at all!

Shallow Copy

Naively, to combat this, one would probably do something like this:

const myArr = [1, 2, 3];
let copy = [...myArr];

copy[3] = 4;
console.log(copy, myArr); // [1, 2, 3, 4], [1, 2, 3]

And all is good! However, is it though?

const myArr = [{a:1}, {a:2}, {a:3}];
let copy = [...myArr];

copy[2].a = 4;
console.log(copy, myArr); // [{a:1},{a:2}, {a:4}], [{a:1},{a:2}, {a:4}]

That's right, it only made a shallow copy. Only the first things got copied, nothing else!

Deep Copy

To implement a deep copy, all we need to do is deep copy objects within objects. It's quite simple, and here's my implementation of it:

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

If the argument isn't an object (which we will see later), we just return the object as it will be passed by value, and if it is an object, we just create a new object that gets assigned key by key with copies of the value of the key.

Now, our example looks like this:

const myArr = [{a:1}, {a:2}, {a:3}];
let copy = copyObj(myArr);

copy[2].a = 4;
console.log(copy, myArr); // { '0':{a:1}, '1':{a:2}, '2':{a:4}}, [{a:1}, {a:2}, {a:3}]

it copied! However, the output isn't really an array anymore...

Array Copy

For arrays, we just need to map the values, and the function looks something like this:

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

A very nice recursive solution like the object copy.

Conclusion

Everything is passed by value except for objects. Those are stored as pointers, and will be passed by reference. In order to remedy this, we defined two functions.

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

And special thanks to @fuzzyastrocat, here's a function that accomplishes both:

function copyObj(obj) {
  if (typeof obj != "object") return obj;

  let out = {};
  for (const key of Object.keys(obj)) out[key] = copy(obj[key]);
  return out;
}

function copyArr(items) {
  return items.map(item => Array.isArray(item) ? copyArr(item) : item);
}

function copy(obj) {
  if (typeof obj != "object") return obj;
  if (Array.isArray(obj)) return copyArr(obj);
  return copyObj(obj);
}

Now, with these functions at your disposal, go forth and complete the aoc!

You are viewing a single comment. View All
RayhanADev (1215)

I wish I understood any of this ( -.-)

Me before reading this:

Dev.to: Pick your skill level
Me: 7/10

Me after reading this:

Dev.to: Pick your skill level
Me: -1/10