Cloning objects is a common requirement in many software applications, particularly when you need to create copies of data structures without affecting the original objects. In TypeScript, there are several ways to clone objects, each with its own advantages and limitations depending on whether shallow or deep cloning is required.
Introduction
In object-oriented programming (OOP), objects can contain various properties such as primitive values (e.g., numbers, strings) and composite values (e.g., arrays, other objects). Cloning an object means creating a new instance that replicates these properties. The complexity of this operation depends on whether you require shallow or deep cloning.
- Shallow Copy: A shallow copy duplicates the top-level structure of an object but maintains references to the original nested objects.
- Deep Copy: A deep copy replicates every level of nested structures, resulting in entirely independent objects without shared references.
Shallow Cloning Techniques
-
Spread Operator
The spread operator (
...
) is a concise way to create shallow copies of objects:class Example { constructor(public type: string) {} } class Customer { constructor(public name: string, public example: Example) {} greet() { return 'Hello ' + this.name; } } const customer = new Customer('David', new Example('DavidType')); // Shallow clone const shallowClone = { ...customer }; console.log(shallowClone.name); // Output: David
The spread operator works well for simple objects but does not duplicate nested objects.
-
Object.assign()
MethodObject.assign()
is another method to perform shallow copying:const clonedCustomer = Object.assign({}, customer); console.log(clonedCustomer.name); // Output: David
Like the spread operator,
Object.assign()
will not create copies of nested objects; it only duplicates references. -
Object.create()
MethodUnlike other methods,
Object.create()
doesn’t directly clone properties but sets the prototype:const createdCustomer = Object.create(customer); console.log(createdCustomer.__proto__.name); // Output: David
This method is more about establishing prototype chains than cloning objects.
Deep Cloning Techniques
-
Recursive Deep Copy Function
A recursive function can perform deep copying by iterating over each property:
function deepCopy(obj) { if (null == obj || "object" != typeof obj) return obj; let copy; if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } if (Array.isArray(obj)) { copy = []; for (let i = 0, len = obj.length; i < len; i++) { copy[i] = deepCopy(obj[i]); } return copy; } if (obj instanceof Object) { copy = {}; for (const attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } const deepClonedCustomer = deepCopy(customer); console.log(deepClonedCustomer.name); // Output: David
This function handles primitive values, arrays, and objects but not functions or circular references.
-
JSON Serialization
JSON serialization is a straightforward way to clone an object deeply:
const jsonClone = JSON.parse(JSON.stringify(customer)); console.log(jsonClone.name); // Output: David
While effective for many cases, this method has limitations with non-serializable properties like functions and
Date
objects.
Advanced Considerations
-
Structured Clone Algorithm
In modern JavaScript environments, the
structuredClone()
function offers a robust way to deep clone:const structuredClone = structuredClone(customer); console.log(structuredClone.name); // Output: David
This method supports various complex types and is highly efficient but may not be available in all environments.
-
TypeScript Custom Clone Methods
You can enhance type safety by implementing custom clone methods within your classes:
class Customer { constructor(public name: string, public example: Example) {} getCopy(): Customer { return JSON.parse(JSON.stringify(this)); } } const customerClone = customer.getCopy(); console.log(customerClone.name); // Output: David
Conclusion
Choosing the right cloning technique in TypeScript depends on your specific use case, such as whether you need shallow or deep copies and how complex your object structures are. Understanding these methods allows developers to effectively manage data duplication without unintended side effects.