Cloning Objects and Class Instances in JavaScript

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.


Erik August Johnson is a software developer working with JavaScript to build useful things. Follow them on Twitter.