What is Shallow Copy and Deep Copy in JavaScript?

What is Shallow Copy and Deep Copy in JavaScript?

ยท

6 min read

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 ๐Ÿคฏ

Did you find this article valuable?

Support Rahul by becoming a sponsor. Any amount is appreciated!