Introduction
With the rise of the use of immutablility in JavaScript, cloning data structures has risen along with it. With objects being reference types, we can’t use =
to make a copy.
Let’s look at several ways to clone objects as well as to clone class instances.
Object
First, let’s look at solutions for cloning generic objects.
Spread Operator
The spread operator (...
) can be used to shallow-clone an object.
const cat = { meows: true, name: 'Cal' };
const clone = { ...cat };
console.log(clone);
// { meows: true, name: 'Cal' }
cat === clone
// false
It also works for arrays:
let numbers = [1, 2, 3];
let clone = [...numbers];
clone.push(4);
console.log(numbers);
// [1, 2, 3]
Object.assign
The next is Object.assign
. The function takes in two parameters, target and sources. In the case of cloning we can set the target to {}
and the source(s) to the object we want to clone. For example:
const dog = { barks: true, name: 'Benny' };
const clone = Object.assign({}, dog);
console.log(clone);
// { barks: true, name: 'Benny' };
dog === clone
// false
JSON
A bit of a hack, but stringifying and parsing an object results in a deeply cloned object. Let’s take a look:
const mouse = { squeeks: true, name: 'Mousey' };
const clone = JSON.parse(JSON.stringify(mouse));
console.log(clone);
// { squeeks: true, name: 'Mousey' }
3rd Party Libraries
If you don’t mind 3rd party libraries, lodash.deepClone
performs a recursive “deep” clone - meaning nested properties, documentation is here.
Other Libraries:
jQuery: $.extend()
Underscore: _.clone()
Class Instance
Object
What about a class instance? If we use the above solutions, we will lose the type information and get back an Object
. This could be a problem, especially in TypeScript.
Here’s one approach to cloning while preserving type:
let clone = Object.assign(Object.create(Object.getPrototypeOf(original)), original)
This works in its simplest form, but is hard to look at!
Lodash
Lodash is a pretty awesome collection of helper functions, and it turns out cloneDeep
will preserve the instance of a class!
import * as _ from 'lodash';
class Cat {
constructor(public meows: boolean, public name: string) {}
}
const cat = new Cat(true, 'Cal');
const clone = _.cloneDeep(cat);
console.log(clone instanceof Cat); // true
console.log(clone === cat); // false
Instance Method
One other way to clone an instance is through adding a clone
function to the class. For example:
class Cat {
constructor(public meows: boolean, public name: string) {}
clone() {
return new Cat(this.meows, this.name);
}
}
const cat = new Cat(true, 'Cal');
const clone = cat.clone();
console.log(clone instanceof Cat);
console.log(clone === cat);
This method works nicely along with being fairly tidy.
Conclusion
There are several valid ways to clone an object as well as a class instance - though the most robust is Lodash’s cloneDeep
which not only clones recursively but preserves the class instance type. If you don’t want a third party dependency, you have some other options.