I just discovered the structuredClone() method. It is a native browser function in JavaScript that will perform a deep clone of objects. Let's demo it!

First, create a few classes:

constructor(value1, value2) { this.nestedValue1 = value1; this.nestedValue2 = value2; } }

I'll start with a class named NestedClass. This class will be nested inside another class, because when deep cloning I want to make sure that nested objects are cloned by value and not by reference. This class has two values in it.

Next, create the outer class:

view plain print about
1class WrapperClass {
2 value1;
3 value2;
4 nestedObject1;
5 nestedObject2;
6
7 constructor(value1, value2, value3, value4) {
8 this.value1 = value1;
9 this.value2 = value2;
10 this.nestedObject1 = value3;
11 this.nestedObject2 = value4
12 }
13}

This is a WrapperClass of the NestedClass. It is expected to contain two simple values, and two complex values representing NestedClass instances.

In situations like this, I'm starting to miss TypeScript.

Let's create an object instance:

view plain print about
1let nestedValue1 = resetNestedValue1('nestedClass1Value1', 'nestedClass1Value2');
2let nestedValue2 = resetNestedValue2('nestedClass2Value1', 'nestedClass2Value2');
3let myObject = new WrapperClass('nonNestedValue1', 'nonNestedValue2', nestedValue1, nestedValue2)

Then output the console log:

view plain print about
1console.log(myObject);

You should see something like this:

We have nicely typed objects, with the NestedObject values as properties inside the external object.

Now clone:

view plain print about
1let myClonedMyObject = structuredClone(myObject);

And output the cloned Object:

view plain print about
1console.log(myClonedMyObject );

This is where I lost my love for the structuredClone() method:

It did not retain the class; instead converting the classes into generic objects. If we had custom methods, or getter and setters inside the original object, then we lost those in the conversion. Bummer!

However, we do have a deep nested clone of the objects, and can change one without changing the other.

view plain print about
1console.log(myObject === myClonedMyObject ); // expect false
2console.log(myObject.nestedObject1 === myClonedMyObject.nestedObject1 ); // expect false
3console.log(myObject.nestedObject2 === myClonedMyObject.nestedObject2 ); // expect false

The Equality check returns false in all cases, even for the two nested properties:

We can change a few simple values to see what happens:

view plain print about
1console.log(myObject.value1); // expect nonNestedValue1
2console.log(myClonedMyObject.value1 ); // expect nonNestedValue1
3console.log(myObject.value1 === myClonedMyObject.value1 ); // expect true
4
5// Change simple value on Object1
6myObject.value1 = 'nonNestedValue1Modified';
7
8console.log(myObject.value1); // expect nonNestedValue1Modified
9console.log(myClonedMyObject.value1 ); // expect nonNestedValue1
10console.log(myObject.value1 === myClonedMyObject.value1 ); // expect false

The simple values are still seen as equal, but we can change one without the other, as proven by the second check:

Since we already know that objects are different, I'm not gonna create a sample for modifying those.

Check out my Sample Code Here.