Context
In this article, we will explain the difference between shallow and deep copies of objects in JavaScript and where to use them. First, it is important to understand that data values in JavaScript are stored in two forms: Primitives
(Boolean, Null, Undefined, String, Number, BigInt, and Symbol), which are immutable, and Objects
(object literals, arrays, dates, etc.), which are mutable. Since objects are mutable, certain methods can alter them. Objects are copied by reference, meaning that changing the copied object might also alter the original object.
Example with Primitives:
const testCase = 'TestCase';
testCase[0] = 'B';
console.log(testCase);
// Output: "TestCase"
From the example above, we see that the first letter of testCase didn't change. While this is allowed in non-strict mode, if we enable strict mode, we get an error:
'use strict';
const testCase = 'TestCase';
testCase[0] = 'B';
console.log(testCase);
// Uncaught TypeError: Cannot assign to read only property '0' of string 'TestCase'
Here is an example that proves that objects are mutable:
const testCase = ['T', 'e', 's', 't'];
testCase[0] = 'B';
console.log(testCase);
// Output: ["B", "e", "s", "t"]
Difference Between Shallow and Deep Copy
Shallow Copy
A shallow copy creates a new object but only copies the reference of nested objects. If the original object contains other objects, only their references are copied, not the actual nested objects.
Example
Using Object.assign
or the spread operator (...) creates a shallow copy:
const original = { name: 'Kastriot', address: { city: 'Vienna' } };
const shallowCopy = { ...original };
shallowCopy.address.city = 'Berlin';
console.log(original.address.city);
// Output: "Berlin"
Deep Copy
A deep copy creates a new object and recursively copies all nested objects, ensuring complete duplication of the original object. Changes to the deep copy do not affect the original object.
Example
Using libraries like lodash
or JSON.parse(JSON.stringify())
and recursive functions can achieve deep copying.
const _ = require('lodash');
const original = { name: 'Kastriot', address: { city: 'Vienna' } };
const deepCopy = _.cloneDeep(original);
deepCopy.address.city = 'Berlin';
console.log(original.address.city);
// Output: "Vienna"
Update (20-10-2024): You can make deep copy naivly using structuredClone()
Since March 2022 structuredClone() method is available in JavaScript. It is a deep copy method that can be used to clone objects, arrays, and other data types. It is a more efficient and reliable way to create deep copies of objects.
const original = { name: 'Kastriot', address: { city: 'Vienna' } };
const deepCopy = structuredClone(original);
deepCopy.address.city = 'Berlin';
console.log(original.address.city);
// Output: "Vienna"
You can read more about structuredClone() here.
Use Cases
- Shallow Copy: Suitable for simple objects or when only top-level properties need duplication.
- Deep Copy: Necessary for complex objects with nested structures to ensure complete independence from the original object.
Performance
- Shallow Copy: Faster and less resource-intensive.
- Deep Copy: Slower and more resource-intensive due to recursive copying.
Conclusion
Understanding the differences between shallow and deep copies is crucial for effectively managing and manipulating objects in JavaScript. By choosing the appropriate copy method, you can optimize performance and ensure the integrity of your data. Whether you need a quick, shallow copy or a thorough, deep copy, JavaScript provides the tools to meet your needs.
Photo by Ralph Mayhew on Unsplash