In JavaScript (and TypeScript) understanding the concepts of deep copy and shallow copy is essential for maintaining data integrity.
Considering these models:
interface Jedi {
name: string;
species: Species;
powers: Powers;
}
interface Powers {
abilities: string[];
}
type Species = 'Human' | 'Alien';
Shallow copy entails creating a new object and copying property values from the original object into the new one. However, it falls short when it comes to preserving the integrity of nested objects. Let's examine a scenario that highlights the limitations of shallow copy:
const anakin: Jedi = {
name: "Anakin Skywalker",
species: 'Human',
powers: {
abilities: ["Lightsaber combat", "Force push"],
},
};
const darthVader: Jedi = {...anakin}; // Shallow copy
darthVader.name = "Darth Vader";
darthVader.powers.abilities.push("Force choke");
console.log(anakin.name); // Output: "Anakin Skywalker"
console.log(anakin.powers.abilities); // Output: ["Lightsaber combat", "Force push", "Force choke"]
As shown by outputs spread syntax effectively goes one level deep while copying an object.
Therefore, it may be unsuitable for copying nested object. The same is true with Object.assign()
and Object.create()
.
To achieve a true and independent copy of an object, including nested properties, we need to employ deep copy techniques.
We can leverage serialization and deserialization to accomplish this.
Let's examine an approach using JSON.stringify()
and JSON.parse()
:
const luke: Jedi = {
name: "Luke Skywalker",
species: "Human",
powers: {
abilities: [],
},
};
const trainedLuke: Jedi = JSON.parse(JSON.stringify(luke)); // Deep copy with JSON.stringify and JSON.parse
trainedLuke.powers.abilities.push("Lightsaber combat", "Force push");
console.log(luke.powers.abilities); // Output: []
As shown by output after the serialization/deserialization process a deep copy shares no references with its source object, any changes made to the deep copy do not affect the source object.
Until now no native operation in JavaScript does a deep clone but the new web API method structuredClone()
allows deep copying values of certain supported types.
Let's examine an approach using this api:
const yoda: Jedi = {
name: "Yoda",
species: "Alien",
powers: {
abilities: ["Lightsaber combat", "Force push"],
},
};
const trainedYoda: Jedi = structuredClone(yoda); // Deep copy with structuredClone
trainedYoda.powers.abilities.push("Speaking clearly");
console.log(yoda.powers.abilities); // Output: ["Lightsaber combat", "Force push"]
As developers, just as we grasp the distinction between pointers and values in programming, understanding the difference between deep copy and shallow copy becomes crucial when handling complex objects. Shallow copy mirrors the surface, while deep copy empowers us to create independent replicas, even for nested objects. Equipped with this knowledge, we ensure data consistency and uphold the integrity of our code.