In-depth Shallow Copy and Deep Copy

Hello readers, there might be scenarios where we as a programmer/developer want to copy the values of the variables and use that variable in another task. In this case, we need to copy the value of that variable into another variable.

In JavaScript there are two ways in which data is copied, namely:

pass by value

The actual value is passed

pass by reference

The memory location where the data is stored is passed

Also, there are two types of data types in Javascript, namely:

primitive data type

This includes Boolean, NULL, undefined, String and Number

reference/ non-primitive data type

This includes Array, Objects and Functions.

Copy by equating "="

The primitive data type values are copied by value, whereas the non-primitive data type values are copied by reference (i.e., the memory location of the data type is copied instead of the value)

Equating in case of primitive data type:

const fruit = "apple"
const copiedFruit = fruit
console.log(fruit, copiedFruit)

Output:

apple
apple
// Nothing to consider in case of primitive data type

Let's check in case of reference data types,

Equating in case of reference data type:

const fruit = ["apple", "mango", "banana"];
const copiedFruit = fruit    // just reference to the memory location is copied

copiedFruit[0] = "orange"

console.log(fruit, copiedFruit)

Output:

Output: 
[ 'orange', 'mango', 'banana' ]  // source array got changed aswell
[ 'orange', 'mango', 'banana' ]

Since this is copied by reference, changing the value of copiedFruit changes the value of fruit (source array) as well. This is true for the objects and the functions as well as shown in the above code snippet.

array1.png The image depicts that actually the object (array) reference is copied, i.e., the object (array) points to the same memory location.

But, we don’t want to mutate the source array or objects. This can be achieved by the following in-built javascript methods:

Copy by using Javascript Methods

Using array methods

By using slice()

const fruit = ["apple", "mango", "banana"];
const copiedFruit = fruit.slice(0)    // creates an actual copy of array 

copiedFruit[0] = "orange"

console.log(fruit, copiedFruit)

Output

// Output: 
[ 'apple', 'mango', 'banana' ]         // didn't affected the source array
[ 'orange', 'mango', 'banana' ]

By using ES6 Spread operator:

const fruit = ["apple", "mango", "banana"];
// Only change in below line
const copiedFruit = [...fruit]   // creates an actual copy of array 

copiedFruit[0] = "orange"

console.log(fruit, copiedFruit)

Output

// Output: 
[ 'apple', 'mango', 'banana' ]         // didn't affected the source array
[ 'orange', 'mango', 'banana' ]

By using concat()

const fruit = ["apple", "mango", "banana"];
// Only change in below line
const copiedFruit = fruit.concat([])   // creates an actual copy of array 

copiedFruit[0] = "orange"

console.log(fruit, copiedFruit)

Output

// Output: 
[ 'apple', 'mango', 'banana' ]         // didn't affected the source array
[ 'orange', 'mango', 'banana' ]

By using Array.from()

const fruit = ["apple", "mango", "banana"];
// Only change in below line
const copiedFruit = Array.from(fruit)   // creates an actual copy of array 

copiedFruit[0] = "orange"

console.log(fruit, copiedFruit)

Output

// Output: 
[ 'apple', 'mango', 'banana' ]         // didn't affected the source array
[ 'orange', 'mango', 'banana' ]

Using Object Method

by using Object.assign

const fruitObj = {name: "apple", color: "red"};
const copiedFruitObj= Object.assign({}, fruitObj) // creates an actual copy of array 

copiedFruitObj.color = "yellow"

console.log(fruitObj, copiedFruitObj)

Output

// Output: 
{ name: 'apple', color: 'red' }  //fruitObj: didn't affected the source object
{ name: 'apple', color: 'yellow' }   //copiedFruitObj

shallow.png As shown in the image above, memory locations for fruit and copiedFruit are different and thus all the source reference data types are not mutated.

So far so good, but the problem arises when any of the reference data types have a nested array, object or function. Before discussing the problem let's talk about shallow copy.

All the ways of copying arrays or objects discussed so far are performing the shallow copy.

What is SHALLOW COPY?

According to the mdn docs,

A shallow copy of an object is a copy whose properties share the same references (point to the same underlying values) as those of the source object from which the copy was made.

Let’s understand shallow copy:

let’s take the same fruit array example from above:

const fruit = [{name: "cherry"},"apple", "mango", "banana"];
// In the fruit array the first element of the array is a reference data type (object)
// Now, replacing the first element
const copiedFruit = [...fruit]
copiedFruit[0] = "orange";
console.log(fruit, copiedFruit)

Output:

 // Output: 
[ { name: 'cherry' }, 'apple', 'mango', 'banana' ]   //fruit 
[ 'orange', 'apple', 'mango', 'banana' ]             //copiedFruit: independent copy

In the above snippet, still both the array are completely independent (the source array is not mutated due to the copied array), but the problem arises when the properties of the nested reference data type (array or object) are changed as shown in below code snippet.

const fruit = [{name: "cherry"},"apple", "mango", "banana"];
// In the fruit array the first element of the array is a reference data type (object)
const copiedFruit = [...fruit]
// Now, changing the property of the nested reference data type
copiedFruit[0].name = "orange";      
console.log(fruit, copiedFruit)

Output:

 // Output: 
[ { name: 'cherry' }, 'apple', 'mango', 'banana' ]   //fruit: source mutated
[ 'orange', 'apple', 'mango', 'banana' ]             //copiedFruit

Let's break down how this is happening: In Shallow Copy, as shown in image below,

  1. if the array elements or object keys value are of primitive data type, they are copied by values, and,
  2. if the properties of the array elements or object keys values are of reference data type (array or object) they are copied by reference.

shallow copy.png

So, now how can we create a completely independent copy of objects/arrays which don’t mutate the source object. Here comes the deep copy,

What is DEEP COPY?

According to the mdn docs,

A deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object from which the copy was made.

In simpler terms, copying the data stored from the source memory location to a new memory location by creating a completely independent object.

how to perform deep copy?

Following are the ways to perform the deep copy:

METHOD 1: JSON.parse(JSON.stringify(object)))

  • JSON.stringify() converts a JavaScript object into a JSON string and then returns it.
  • JSON.parse() converts a JSON literal string into a JavaScript object and then returns it.

Now, using the previous example and applying JSON.parse(JSON.stringify(object))

const fruit = [{name: "cherry"},"apple", "mango", "banana"];
// In the fruit array the first element of the array is a reference data type (object)
const copiedFruit = JSON.parse(JSON.stringify(fruit))
// Now, changing the property of the nested reference data type
copiedFruit[0].name = "orange";      
console.log(fruit, copiedFruit)

Output:

 // Output: 
[ { name: 'cherry' }, 'apple', 'mango', 'banana' ]  //fruit 
[ { name: 'orange' }, 'apple', 'mango', 'banana' ]  //copiedFruit: independent copy

This is fine, provides a deep copy and creates a new copy of data in a different memory location.

But when Date, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types are used within your object, this method of cloning doesn't work as expected. As shown in following code snippet:

const fruit = [{name: "cherry"},"apple", "mango", "banana", function () {console.log("performs some task")}];
// In the fruit array the first element of the array is a reference data type (object)
const copiedFruit = JSON.parse(JSON.stringify(fruit))
// Now, changing the property of the nested reference data type
copiedFruit[0].name = "orange";      
console.log(fruit, copiedFruit)

Output:

 // Output: 
[ { name: 'cherry' }, 'apple', 'mango', 'banana', [Function] ]  //fruit 
[ { name: 'orange' }, 'apple', 'mango', 'banana', null ]  //copiedFruit: missing function

Here as you can see in the above code snippet, the function is replaced by null, this method of the deep clone is not full proof!

What is the full proof method of the deep clone?

METHOD 2: Using external libraries:

  • Lodash: It is a library that has a method called cloneDeep, which does exactly what it states: Creates a deep clone of the reference data type passed and It also takes care of nested reference data types.

Reference for clone deep: cloneDeep Method

Summary:

  1. primitive data types are passed by value & non-primitive data types are passed by reference.
  2. In shallow cloning, the nested reference data types share the same memory location as their copied counterparts. JavaScript Methods like slice(), spread operator, concat(), Array.from() and Object.assign() performs shallow cloning.
  3. JSON.parse(JSON.stringify)) can perform deep cloning with exceptions of functions, date objects and so on, which when included doesn't give expected results.
  4. To get full proof deep cloned object, an external library such as Lodash should be used which provides a deepClone method.