Hey guys in this latest post we will understand about Shallow Copy and Deep Copy in JavaScript in a very easy way. I was writing this post from the last 5 days(on my phone) and I completed it fully. I think you can make a benefit from it.
In my ongoing long journey of JavaScript, I have discovered many things and found very useful and want to share it.
Let's Go !!!
What is Shallow Copy?
- Making a shallow copy of an array or object means creating new references to the primitive values inside the object, copying them.
- That means that changes to the original array will not affect the copied array, which is what would happen if the only reference to the array had been copied (such as would occur with the assignment operator =).
A shallow copy refers to the fact that only one level is copied, and that will work fine for an array or object containing only primitive values.
- For objects and arrays containing other objects or arrays, copying these objects requires a deep copy. Otherwise, changes made to be nested references will change the data nested in the original object or array.
Example
const array = ['๐', '๐', '๐']
const copyWithEquals = array
console.log(copyWithEquals === array) // true (The assignment operator did not make a copy)
array[0] = '๐ก'
console.log(...array) // ๐ก ๐ ๐
console.log(...copyWithEquals) // ๐ก ๐ ๐
-> In order to avoid the above this problem, Let's see 4 methods of a shallow copy.
Shallow copy using (...)
The spread operator (...) is a convenient way to make a shallow copy of an array or object when there is no nesting, it works great.
const array = ['๐', '๐', '๐']
const copyWithEquals = array
console.log(copyWithEquals === array) // true (The assignment operator did not make a copy)
const copyWithSpread = [...array] // Changes to array will not change copyWithSpread
console.log(copyWithSpeed === array) // false (The spread operator made a shallow copy)
array[0] = '๐ก'
console.log(...array) // ๐ก ๐ ๐
console.log(...copyWithEquals) // ๐ก ๐ ๐
console.log(...copyWithSpread) // ๐ ๐ ๐
Shallow copy using .slice()
For arrays specifically, using the built-in .slice()
method works the same as the spread operator - creating a shallow copy of one level:
const array = ['๐', '๐', '๐']
const copyWithEquals = array
console.log(copyWithEquals === array) // true (The assignment operator did not make a copy)
const copyWithSlice = array.slice() // changes to array will not change copyWithSlice
console.log(copyWithSlice === array) // false (Using .slice() made a shallow copy of the array)
array[0] = '๐ก'
console.log(...array) // ๐ก ๐ ๐
console.log(...copyWithEquals) // ๐ก ๐ ๐
console.log(...copyWithSlice) // ๐ ๐ ๐
Shallow copy using .assign()
The same type of shallow copy would be created using Object.assign(), which can be used any object or array:
const array = ['๐', '๐', '๐']
const copyWithEquals = array
const copyWithAssign = [] // Changes to array will not change copyWithAssign
Object.assign(copyWithAssign, array) // Object.assign(target, source)
array[0] = '๐ก'
console.log(...array) // ๐ก ๐ ๐
console.log(...copyWithEquals) // ๐ก ๐ ๐
console.log(...copyWithAssign) // ๐ ๐ ๐
Shallow copy arrays using Array.from()
Another method to copy a JavaScript array using Array.from(), which will laso make a shallow copy, as shown in the below example:
const emojiArray = ["๐", "๐", "๐"]
console.log(...emojiArray) // ๐ ๐ ๐
const emojiArrayNotCopied = emojiArray
console.log(emojiArrayNotCopied === emojiArray) // true
// Make a shallow copy using Array.from:
const emojiArrayShallowCopy = Array.from(emojiArray)
console.log(emojiArrayShallowCopy === emojiArray) // false
eemojiArray[0] = "๐ก"
console.log(...emojiArray) // ๐ก ๐ ๐
console.log(...emojiArrayNotCopied) // ๐ก ๐ ๐
console.log(...emojiArrayShallowCopy) // ๐ ๐ ๐
Hoping you understand many things here and will use this in your projects.
โก
What is Deep Copy?
- For objects and an array containing other objects or arrays, copying thee objects requires a deep copy.
Otherwise, changes made to the nested references will change the data nested in the original object or array. - This is compared to a shallow copy, which works fine for an object or array containing only primitive values, but will fail for any object or array that has nested references to other objects or arrays.
- Understanding the differences between
==
and===
can help visually see the difference between shallow and deep copy, as the strict equality operator (===) shows that the nested references are the same:
Example
const nestedArray = [["๐", "๐", "๐"]]
const nestedCopyWithSpeed = [...nestedArray]
console.log(nestedArray[0] === nestedCopyWithSpread[0]) // true -- Shallow Copy (Same reference)
// This is a hack to make a deep copy that is not recommended because it will often fail:
const nestedCopyWithHacl = JSON.parse(JSON.stringify(nestedArray))
console.log(nestedArray[0] === nestedCopyWithHack[0]) // false -- Deep Copy (Different references)
Let's see some methods for making a deep copy
Deep copy with lodash
The library lodash is the most common way JavaScript Developers make a deep copy. It is surprisingly easy to use:
import_from "lodash" // Import the entire lodash library
const nestedArray = [["๐", "๐", "๐"]]
const notACopyWithEquals = nestedArray
console.log(nestedArray[0] === notACopyWithEquals[0]) // true
const shallowCopyWithSpread = [...nestedArray]
console.log(nestedArray[0] === shallowCopyWithSpread[0]) // true
const shallowCopyWithLodashClone = _.clone(nestedArray)
console.log(nestedArray[0] === shallowCopyWithLodashClone[0]) // true
const shallowCopyWithLodashCloneDeep = _.cloneDeep(nestedArray)
console.log(nestedArray[0] === shallowCopyWithLodashCloneDeep[0]) // false
nestedArray[0] = "๐ง"
nestedArray[2][0] = "๐ก"
console.log(...nestedArray) // ๐ง ["๐"]["๐ก"]
console.log(...notACopyWithEquals) // ๐ง ["๐"]["๐ก"]
console.log(shallowCopyWithSpread) // ["๐"]["๐"]["๐ก"]
console.log(...shallowCopyWithLodashClone) // ["๐"]["๐"]["๐ก"]
console.log(...deepCopyWithLodashCloneDeep) // ["๐"]["๐"]["๐"]
Deep copy with Ramda
The functional programming library Ramda includes the R.clone() method, which makes a deep copy of an object or array.
import R from "ramda" // Import the entire ramda library
const nestedArray = [["๐", "๐", "๐"]]
const notACopyWithEquals = nestedArray
console.log(nestedArray[0] === notACopyWithEquals[0]) // true
const shallowCopyWithSpread = [...nestedArray]
console.log(nestedArray[0] === shallowCopyWithSpread[0]) // true
const deepCopyWithRamdaClone = R.clone(nestedArray)
console.log(nestedArray[0] === deepCopyWithRamdaClone[0]) // false
nestedArray[0] = "๐ง"
nestedArray[2][0] = "๐ก"
console.log(...nestedArray) // ๐ง ["๐"]["๐ก"]
console.log(notACopyWithEquals) // ๐ง ["๐"]["๐ก"]
console.log(shallowCopyWithSpread) // ["๐"]["๐"]["๐ก"]
console.log(...deepCopyWithRamdaClone) // ["๐"]["๐"]["๐"]
Read more - What Is Shallow Copy ?
Deep copy with JSON.parse/stringify
If your data fits the specifications, then JSON.parse followed by JSON.strigify will deep copy your object.
// Only some of these will work with JSON.parse() followed by JSON.stringify()
const samplwObject = {
string: 'string',
number: 123
boolean: false,
null: null,
notANumber: NaN, // NaN values will be lost (The value will be forced to 'null')
date: new Date('2020-12-31T23:59:59')
undefined: undefined, // Undefined values will be completely lost, including the key containing the undefined value
infinity: Infinity, // Infinity will be lost (the value will be forced to 'null' )
regExp: /.*/, // regExp will be lost (the value will be forced to an empty object{})
}
console.log(sampleObject) // Object
// {
// string: "string", number: 123, boolean: false, null: null, notANumber: NaN, date: Date Thu Dec 31 2020 23:59:59, undefined: undefined, infinity: Infinity, regExp: /.*/
// }
console.log(typeof sampleObject.date) // object
const faultyClone = JSON.parse(JSON.stringify(sampleObject))
console.log(faultyClone) // Object
// {
// string: "string", number: 123, boolean: false, null: null, notANumber: null, date: Date Thu Dec 31 2021 04:59:59, infinity: null, regExp: {}
// }
// The date object has been stringified. the result of .toISOString()
console.log(typeof faultyCloen.date) // string
If you don't wanna use Dates, functions, RegExps, Maps, Sets, Blobs, undefined, Infinity, [NaN], sparse arrays, typed arrays or any other complex types within your object then there is a very simple one-liner to deep clone an object is:
JSON.parse(JSON.stringify(object))
Thanks For Reading | Happy Coding ๐คฏ