structuredClone vs lodash.cloneDeep: What Happens with Abstract Class Instances
Analysis of behavioral differences between structuredClone and lodash.cloneDeep when deep copying TypeScript abstract class instances
structuredClone vs lodash.cloneDeep: What Happens with Abstract Class Instances
When working with TypeScript, you often need to deep copy instances that extend abstract classes. I compared the native Web API structuredClone with lodash’s _.cloneDeep and discovered they behave completely differently.
The Problem
abstract class BaseEntity {
constructor(public id: string) {}
abstract getDescription(): string;
}
class User extends BaseEntity {
constructor(
id: string,
public name: string,
public email: string
) {
super(id);
}
getDescription(): string {
return `User: ${this.name} (${this.email})`;
}
}
const originalUser = new User('1', 'Kim', 'kim@example.com');
Now we need to deep copy originalUser.
Experiment 1: structuredClone
const clonedWithStructured = structuredClone(originalUser);
console.log(clonedWithStructured instanceof User); // false ❌
console.log(clonedWithStructured.constructor.name); // "Object" ❌
console.log(clonedWithStructured.getDescription); // undefined ❌
Result: structuredClone loses the class prototype and converts it to a plain Object.
Experiment 2: lodash.cloneDeep
import _ from 'lodash';
const clonedWithLodash = _.cloneDeep(originalUser);
console.log(clonedWithLodash instanceof User); // true ✅
console.log(clonedWithLodash.constructor.name); // "User" ✅
console.log(clonedWithLodash.getDescription()); // "User: Kim (kim@example.com)" ✅
Result: _.cloneDeep preserves the class prototype and copies correctly.
Why Does This Happen?
structuredClone Limitations
structuredClone follows the Structured Clone Algorithm. This algorithm only supports specific types and does not support custom classes.
Supported types:
- Array, Object (plain)
- Date, RegExp, Map, Set
- ArrayBuffer, TypedArray
- Blob, File, FileList
- ImageData, etc.
Unsupported types:
- Function
- DOM Nodes
- Custom class instance prototype information
Therefore, when copying a class instance, the prototype chain is broken and it becomes a plain object.
How lodash.cloneDeep Works
lodash operates at JavaScript runtime and references the object’s constructor to create a new instance, preserving prototype information.
// Simplified lodash internal logic
function cloneDeep(value) {
if (isObject(value)) {
const Ctor = value.constructor;
const result = new Ctor();
// Copy properties...
return result;
}
return value;
}
Copy Constructor Pattern
In previous projects, we used the copy constructor pattern to avoid this issue.
class User extends BaseEntity {
constructor(
id: string,
public name: string,
public email: string
) {
super(id);
}
// Copy constructor
static from(other: User): User {
return new User(other.id, other.name, other.email);
}
clone(): User {
return User.from(this);
}
}
const copied = originalUser.clone();
console.log(copied instanceof User); // true ✅
This approach is explicit and safe, but requires implementation in every class.
Recommendations
| Situation | Recommended Method |
|---|---|
| Plain object copying | structuredClone ✅ |
| Class instance copying | _.cloneDeep or copy constructor |
| Data transfer between Web Workers | structuredClone (serializable types only) |
| When performance matters | structuredClone (native, faster) |
Summary
structuredClonedoes not preserve custom class prototypes._.cloneDeepcorrectly copies class instances.- Be clear about which copy method to use when dealing with TypeScript abstract classes.
- Consider the copy constructor pattern for safety.
💡 TL;DR: Use
lodash.cloneDeepwhen you need to deep copy class instances.structuredCloneloses the prototype.