Samuel
Samuel

Reputation: 135

How to deep copy (clone) an object with array members in Javascript?

Introduction

I have a Class Persons that contains an array of Person and functions :

function Persons() {
  this.mItems = []; // Array of Objects Person
}

Persons.prototype = {
  calculateScores : function() {
    // Do some stuff
  }
}

The Class Person has members and functions :

function Person(name) {
  this.name = name; // Name of the Person
  this.score = 0;
}

Person.prototype = {
  calculateScore : function() {
    // Do some stuff
  }
}

I want that the program does the following things :

var persons = new Persons();
var person0 = new Person("John");
var person1 = new Person("Sam");
persons.mItems.push(person0);
persons.mItems.push(person1);

// Completely clone the original Objects
clonedPersons = persons.clone(); // I am looking for a clone() function

// Modify an item in the cloned Objects
clonedPersons.mItems[0].name = "Mick";

// Check that the original Objects have not been modified
console.log(persons.mItems[0].name); // John : Not modified
console.log(clonedPersons.mItems[0].name); // Mick

Question

I want to deep copy an instance of Persons to completely duplicate the Array of Person. The Objects Person must be duplicated. The functions of the Objects must be kept.

JQuery.extend()

JQuery.extend(true, {}, persons) clones the direct members of Persons but shallow copies the Person Objects.

console.log(persons.mItems[0].name); // Mick : Where is John ?!
console.log(clonedPersons.mItems[0].name); // Mick

JSON.parse(json.stringify())

clonedPersons = JSON.parse(json.stringify(persons)) clones the Objects but remove the functions.

persons.mItems[0].calculateScore(); // Does not exists !!!

Thank you for your answers.

Upvotes: 2

Views: 1536

Answers (2)

nrabinowitz
nrabinowitz

Reputation: 55678

If you're dealing with custom classes, you're going to want to implement custom clone methods. Generally, in this context, I'd have 2 separate clone functions, one on the Person model and one on the Persons collection.

Persons.prototype = {
  clone: function() {
    var clone = new Persons();
    clone.mItems = this.mItems.map(function(person) {
        return person.clone();
    });
    return clone;
  }
}

Person.prototype = {
  clone: function() {
    var clone = new Person(this.name);
    clone.score = this.score;
    return clone;
  }
}

The advantage to this approach is that it separates the concerns - the Person class doesn't have to know how to clone a single Person, it only has to know that Person exposes a clone method. If Person adds a new property that should be persisted in the clone, only Person needs to change.

It's generally an anti-pattern to use generic clone methods, e.g. from jQuery or Underscore, in this context. They'll end up cloning things you don't want, or missing things you do (e.g. a Person might eventually have an Address or some other object that will also need cloning).

Upvotes: 3

Oriol
Oriol

Reputation: 288710

You can use [].map and Object.assign:

Persons.prototype.clone = function() {
   var clone = new Persons();
   clone.mItems = this.mItems.map(function(person) {
     return Object.assign(new Person, person);
   });
   return clone;
};

Upvotes: 0

Related Questions