This content originally appeared on Bits and Pieces - Medium and was authored by Jason Kuffler
It’s important to understand what’s happening with Shallow and Deep copies in JavaScript. To understand this, we will take a look at memory allocation and its behavior in JavaScript.
Our goal today is to answer this: why should we be concerned with these concepts and their differences?
If you’re just discovering the shallow copy or looking for clarification then I recommend bookmarking 3 Ways to Shallow Clone Objects in JavaScript . The author’s bonuses are a lot of fun! I like having a JavaScript replit ready to run side-by-side when reading technical docs. It is imperative to have the node environment of your choice running so you can practice while you get a feel for the water.
It’s also wise to track your files somewhere using something like GitHub or Bit. That way you can cloud save your more commonly used, abstracted, and refactored code blocks. Then take out your working bits from replit notes/exercises and create the refactored features inside of a robust space that GitHub or Bit can track. Once you’ve got a feature or solution working, clean, and scalable; then, it can likely be used elsewhere in your work — so why not make it a green square? My goal with this generalized, mutable procedure is to satisfy best practices for modular building with reusable code in addition to understanding what’s operating under the hood when implementing solutions as much as possible.
Further reading which helped me create my own examples and explanations (below) can be found at MDN’s JS Memory Management.
I’ve shared the sources which piqued my interests a bit. Now I want to show you how I like to play with code after I’ve digested some material. The included further reading will breeze by if you’re working along with this article.
Below is a basic copy where a memory space is created for new copies — both immutable (when copied) by virtue of their primitive values/data types. In other words: they are assigned/passed by value.
//Primitive value copy
let x = 400
let y = x
x = "This string"
console.log(y) //400
console.log(x) //This string
When y is assigned it’s value it copied x’s value (as it was during run time — before x is reassigned). The important takeaway here is that you can quickly copy a primitive data type’s exact value in a separate memory space by creating and assigning another variable to the variable being copied. Take note of how it is instantiated — const will not allow later changes.
//We can go a little further with the above block and re-assign x to the original value; however, we know they are separate memory spaces with same values
x = y
console.log(x) // 400
console.log(y) // 400
The Main Focus
There are more complex data types and structures we have to work with: reference values — such as objects and arrays (which are a specific type of JS object). An object has accessible properties aka “keys”. These keys have their own values that are available to be modified. JS objects (as non-primitive data types) differ because they have reference values and those values are mutable. This means they share a memory address when shallow copied.
Note: the example below is a shallow object-literal being shallow copied — meaning their values will be the same because they are both references to the same memory space. In JS a “shallow object” is a non-nested, non-primitive JS datatype.
//Shallow Clone of a Shallow Object
let shallowObj = {
key1: 1,
key2: 2,
}
let newObj = shallowObj // a simple reassignment creates shared
// memory for newObj
shallowObj.key1 = 5
console.log(shallowObj) // {key1: 5, key2: 2}
console.log(newObj) // {key1: 5, key2: 2}
Notice that re-assigning value at key1 of shallowObj changes newObj’s key1 value as well. They share a space in memory and have separate variable names! Where shallowObj.key1 is changed we could instead update newObj.key1 and we get the same result.
This is useful in certain situations where we want to represent a bunch of the same properties and values of a particular object; however, later on in run time those shallow cloned objects could require different variable names to work with your app’s goals — passed as different props to other components for different purposes.
I’d love to hear more about how this concept has real-world application. It bears mentioning that understanding this helps later.
Before going on I want to emphasize that a deep copy differs from the shallow copy in this way: the deep copy is allocating a new memory address for some or all (depending on complexity of data) the values from the original variable. A deep copy goes further and passses elements’ values.
Deep Copies of a Shallow-Object
//Using Object.assign()
let myRadio = { podcasts: 19,
albums: 378,
playlists: 44
}
let deepCopyMyRadio = Object.assign( {}, myRadio )
deepCopyMyRadio.playlists = 62 // only changes deepCopyMyRadio
console.log(deepCopyMyRadio) // => { podcasts: 19,
albums: 378,
playlists: 62
}
console.log(myRadio) // => { podcasts: 19,
albums: 378,
playlists: 44
}
ES6 introduced a little syntactic sugar with the Spread Operator. The spread operator is represented by the three consecutive dots “…” and gets used in a few different parts of code. In general the spread operator is making a copy for each (top-level) property of a given object and then spreading them to a new object.
It’s up to us to decide when we would like a shallow or deep copy of a particular nested object. For now we’re showing a deep copy of a shallow object so either the Object.assign() method or the example below will do.
//Deep Copy with spread operator
let myRadio = { podcasts: 9,
albums: 38,
playlists: 4
}
let copyMyRadio = { ...myRadio }
myRadio.albums = 88 //again only changes myRadio
console.log myRadio //=> { podcasts: 9, albums: 88, playlists: 4 }
console.log copyMyRadio //=> {podcasts: 9,albums: 38, playlists: 4}
The trouble is that a spread operator is a limited solution as scale and complexity increases within a project. Spread operator can handle a deep-copy of a shallow object (non-nested — above). With lightweight code blocks the spread operator performs great.
Below we see the spread operator won’t handle the complexity of a nested object as we may expect.
For nested objects the spread operator will provide a deep copy to the first instance of the values but leaves all the nested data as shallow copies sharing a space in memory with original. Take note of this behavior!
Reminder: we, the wizards, decide which is best (shallow or deep copy) for the problem we’re solving/memory usage of a program.
Do we need to pass references alone or their values as well? Is it necessary to create a new piece of memory for a variable or can it share the values of the original object?
A specific use for deep-copy comes to mind: creating a deeply nested object to serve as a template. A template will need to be copied into a separate memory space along with any nested default values.
This way we can make programmatic changes to copies and set ourselves up for dynamic use of an object throughout the project.
Finally we deep copy a deep object using a highly popular method with ready-made tools in ES6. There are others worth understanding found here.
The Solution for Deep Copy of Deep Data: JSON.parse(JSON.stringify())
Here we have an example of my family’s game library. Once upon a time I was able to play a bunch of big-boy video games . . .
The drawback for copying objects/arrays in vanilla JavaScript is with nested values. As you can see: drilling in, while easy enough, can be tedious.
I really liked these other cool uses for the ES6 spread operator with these executions! If you’re wondering what I meant earlier about spread operator being used in different parts of code you should definitely check it out.
This article is a very thorough breakdown of possible uses for copying arrays and objects with other built-in ES6 methods!
All info presented here is to the best of my understanding. Any corrections or feedback are welcome in the comments.
Use any component in all your projects
Up until now, you used to build features hidden inside larger projects.
But what if you were to develop independent features first, and then easily compose and manage them in many applications? Your development will become faster, more consistent, and more scalable every day. Create a component once, and truly use it anywhere to build anything.
OSS Tools like Bit offer a powerful developer experience for building independent components and composing them to applications. You can start small with a nice project, some shared components, or even trying out Micro Frontends. Give it a try →
Shallow Copy vs Deep Copy in JavaScript was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Jason Kuffler
Jason Kuffler | Sciencx (2022-01-21T13:13:48+00:00) Shallow Copy vs Deep Copy in JavaScript. Retrieved from https://www.scien.cx/2022/01/21/shallow-copy-vs-deep-copy-in-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.