DEV Community

Urfan Guliyev
Urfan Guliyev

Posted on

Deep and Shallow Copies in JavaScript

In the functional programming paradigm, immutability is an absolutely important concept for protecting the data flow of a program. According to immutability, a value cannot be changed after it has been created. Otherwise, it will cause a mutation, which is an unwanted side effect. Therefore, we need to be able to copy values ​​into JavaScript without causing any mutations. A copy is successful when the original(source) object and the copied (target) object are independent of each other, such that a change to one does not affect the other.

Before we dive into making copies, it is absolutely essential that we understand how variables store data.

dive-in

There are two types of stored data:

Primitive data types are:

  • Boolean
  • Number
  • String
  • Null
  • Undefined
  • Symbol

When a variable stores a primitive value, any changes made to the copy won’t affect the original. Let's look at an example:

let obj = 1
let objCopy = obj
console.log (objCopy) // 1

objCopy = 2

console.log (objCopy) // 2
console.log (obj) // 1

Enter fullscreen mode Exit fullscreen mode

Reference data types are:

  • Object
  • Array

It becomes more interesting here. Let’s check out all of the methods for copying a variable that stores reference values. There are 2 types of copies:

1.Shallow Copying

A shallow copy means that only the top level is copied and disconnected from the original object. It should only be used for an array or object containing only primitive values.

Note: While most of the methods below can be used for both objects and arrays, .slice() is only used for arrays.

- Using Spread operator

let obj = {
   a: 1,
   b: 2,
}

let objCopy = {... obj}
console.log (objCopy) // { a:1, b:2 }

objCopy.b = 5

console.log (objCopy) // {a: 1, b: 5}
console.log (obj) // { a:1, b:2 }

Enter fullscreen mode Exit fullscreen mode

- Using .slice()

let obj = [ 1, 5 ]

let objCopy = obj.slice()
console.log (objCopy) // [ 1, 5 ]

objCopy[1] = 9

console.log (objCopy) // [ 1, 9 ]
console.log (obj) // [ 1, 5 ]

Enter fullscreen mode Exit fullscreen mode

- Using Object.assign()

When we are using this method to make a copy, we simply pass an empty object as the first argument, then we pass the object to be copied as a second argument.

let obj = {
   a: 1,
   b: 2,
}

let objCopy = Object.assign({}, obj)
console.log (objCopy) // { a:1, b:2 }

objCopy.b = 5

console.log (objCopy) // {a: 1, b: 5}
console.log (obj) // { a:1, b:2 }

Enter fullscreen mode Exit fullscreen mode

In the above, when we changed the value in objCopy object, the original (source) object didn’t change. This means that we successfully created a copy that has no link to the source object.

Let’s check out the next example:

let obj = {
   a: 1,
   b: {
     c: 2
 }
}

let objCopy = Object.assign({}, obj)
console.log (objCopy) // { a:1, b: { c: 2 } }

objCopy.a = 11
objCopy.b.c = 5

console.log (objCopy) // { a:11, b: { c: 5 } }
console.log (obj) //  { a:1, b: { c: 5 } }

Enter fullscreen mode Exit fullscreen mode

In this example, when we changed the value in objCopy object, the original object (obj) also changed. But why?
It failed because our object contains not just a primitive but also a reference value. Object.assign() only copied the primitive value and the reference value is still connected to the source. In this case we should use deep copy methods.

2.Deep Copying

A deep copy means that all values ​​are copied without any reference to the original object. So, if we have an array or object within our object, we should use deep copy methods.

- Using JSON.parse(JSON.stringify(object))

When we use this method, we are simply converting our object to a string (stringify) and then immediately converting it back to an object (parse). Here is an example:

let obj = {
   a: 1,
   b: {
     c: 2
      }
}

let objCopy = Json.parse(Json.stringify(obj))
console.log (objCopy) // { a:1, b: { c: 2 } }

objCopy.b.c = 5

console.log (objCopy) // { a:1, b: { c: 5 } }
console.log (obj) // { a:1, b: { c: 2} }
Enter fullscreen mode Exit fullscreen mode

In addition to the built-in JavaScript method, we can make copies by using libraries:

- Using the __.cloneDeep() method of the library Lodash

let obj = {
   a: 1,
   b: {
     c: 2
      }
}

let objCopy = __.cloneDeep(obj)
console.log (objCopy) // { a:1, b: { c: 2 } }

objCopy.b.c = 5

console.log (objCopy) // { a:1, b: { c: 5 } }
console.log (obj) // { a:1, b: { c: 2} }
Enter fullscreen mode Exit fullscreen mode

Lodash also has a shallow copy method- _.clone()

let obj = {
   a: 1,
   b: 2,
}

let objCopy = _.clone(obj)
console.log (objCopy) // { a:1, b:2 }

objCopy.b = 5

console.log (objCopy) // {a: 1, b: 5}
console.log (obj) // { a:1, b:2 }

Enter fullscreen mode Exit fullscreen mode

- Using the R.clone() method of the library Ramda

let obj = {
   a: 1,
   b: {
     c: 2
      }
}

let objCopy = R.clone(obj)
console.log (objCopy) // { a:1, b: { c: 2 } }

objCopy.b.c = 5

console.log (objCopy) // { a:1, b: { c: 5 } }
console.log (obj) // { a:1, b: { c: 2} }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)