Deep and Shallow Copies in JavaScript
Urfan Guliyev
Posted on November 17, 2019
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.
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
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 }
- 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 ]
- 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 }
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 } }
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} }
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} }
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 }
- 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} }
Posted on November 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.