4

I have a board class:

var board = new Board();

I want to make a clone of the board and run a movePiece function on the new board:

var newBoard = board;
newBoard.movePiece('4a', '3b');

Since I am just assigning it, both board and newBoard will have the moved piece.

How can I clone board to create an exact copy called newBoard that I can move the piece on and the original board will stay the same?


I have tried a couple different methods, such as:

Object.assign({}, orig)

and

function clone(orig) {
    let origProto = Object.getPrototypeOf(orig);
    return Object.assign(Object.create(origProto), orig);
}

from http://www.2ality.com/2014/12/es6-oop.html

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Case Sandberg
  • 180
  • 1
  • 6
  • I wouldn't try to make a copy. I'd make a new object that inherits from it. `return Object.create(orig);` Then you can update the new object with changes that shadow the original. –  Jul 05 '15 at 23:37
  • @squint So I would create a new object and then loop through and assign all the same variables and functions? Could you give me an example? – Case Sandberg Jul 05 '15 at 23:52
  • 4
    The only way to make a reliable clone is to write a method that takes one instance and creates a new one from the properties of that instance. This is because there's no way for an outside agent to know exactly how to treat every instance variable and it's also possible to have private instance variables too. So, just write a new method that creates a new object and copies over relevant properties or a constructor that takes another object and does the same. – jfriend00 Jul 06 '15 at 00:03
  • @jfriend00 like this? Board.prototype.clone = function() { return Object.assign(Object.getPrototypeOf(this), this); }; – Case Sandberg Jul 06 '15 at 00:12
  • Whatever you do, **do not** treat [*this question*](http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object) as a duplicate! – RobG Jul 06 '15 at 00:13
  • No, not at all what I meant. Just write a method that iterates through the relevant properties (not the prototype, the actual properties you want to copy to the new instance). – jfriend00 Jul 06 '15 at 00:13
  • @CaseSandberg: Please show us the definition of your `Board` class. – Bergi Jul 06 '15 at 00:18
  • @CaseSandberg: No, you'd just inherit from the original and the new object will inherit all the properties and methods. It's much more light weight than copying every time. I assume you need to make many of these to emulate possible moves, so that's the approach I'd take. Just doing `return Object.create(orig);` is literally the entire example. –  Jul 06 '15 at 02:58

1 Answers1

6

The only way to make a reliable clone for an arbitrary object is to write a method on that object that takes one instance and creates a new instance from the properties of the original instance.

This is because there's no way for an outside agent to know exactly how to treat every instance variable (what needs to be assigned, what needs to be copied, etc...) and it's also possible to have private instance variables too. So, just write a new method that creates a new object and copies over relevant properties or a constructor that takes another object and does the same.

It is possible to have an object that one could just assign all the properties to a new instance (if all properties were primitives), but that is usually not the case so more intelligence needs to be applied as to how to best assign each property to the new clone. And, an object could have private instance variables (closure variables) that could have to be dealt with manually too from the within some other method.

Here's an example of a relatively simple object that just has an array in it that needs to be copied. This one supports creation of a clone either via the constructor or via a clone method:

function CallbackList(otherObj) {
    // if another object is passed to the constructor, then initialize this
    // new instance to be a copy
    if (otherObj) {
        // copy the array from the other instance
        this.list = otherObj.list.slice(0);
    } else {
        // initalize empty list
        this.list = [];
    }
}

CallbackList.prototype = {
    addListener: function(fn) {
        this.list.push(fn);
    },
    removeListener: function(fn) {
        for (var i = this.list.length - 1; i >= 0; i--) {
           if (this.list[i] === fn) {
              this.list.splice(i, 1);
           }
        }
    },
    fire: function(/* pass args here */) {
        var args = Array.prototype.slice.call(arguments);
        this.list.forEach(function(fn) {
            fn.apply(null, args);
        });
    },
    clone: function() {
        return new CallbackList(this);
    }
};

To understand why you need custom code that is built specifically for your object to create a clone, here are some of the things that could be in the instance data that you may have to treat in an object-specific way:

  1. A reference to a DOM object. Maybe, the clone should have the same reference (just copied) or maybe a new DOM object needs to be created and inserted into the DOM.
  2. References to other objects. Maybe the new clone should have the same reference or maybe a new object needs to be created for the clone.
  3. Array. If it's a reference to a shared array, then both clones should point to the same array. If it's something that is supposed to be a unique piece of instance data that is separate for each object, then the array needs to be copied. If the array itself contains objects, then you have the same questions for the contents of the array too.
  4. Object. Same issues as an array.
  5. Closure variables (instance variables that are private). Same issues as mentioned above with object properties.
jfriend00
  • 683,504
  • 96
  • 985
  • 979