Modifying copy of an object without modifying original object

I have two sketches, and in sketch 1, I have an object that is storing all the relevant data in sketch 1. In sketch 2, I would like to make a copy of the object, and make minor modifications. However, making these minor modifications is also changing sketch 1's object. How can I get it to not do this?

Here's a minimal example:

Output I want: The circles in sketch 1 have the same height, while the circles in sketch 2 have different heights

Output I get: Circles in both sketch 1 and 2 have different heights.

var sketch = function(p) {

  p.eyes = new Eyes();
    p.setup = function() {
        p.createCanvas(200,200);
        p.background(255);
    }
    p.draw = function() {
        p.ellipse(p.eyes.left[0], p.eyes.left[1], 10);
        p.ellipse(p.eyes.right[0], p.eyes.right[1],10);
    }
}

var sketch2 = function(p) {

  p.newEyes = main.eyes;
  p.newEyes.left = [10,10];
  p.newEyes.right = [50,50];

    p.setup = function() {
        p.createCanvas(200,200);
        p.background(255);
    }
    p.draw = function() {
        p.ellipse(p.newEyes.left[0], p.newEyes.left[1], 10);
        p.ellipse(p.newEyes.right[0], p.newEyes.right[1],10);
    }  
}

function Eyes() {
    this.left = [10,50];
    this.right = [50,50]; 
}

var main = new p5(sketch, 'canvasDiv'); 
var main2 = new p5(sketch2, 'canvasDiv');

Answers

  • edited January 2017

    p.newEyes = main.eyes;?! Why don't you use the new operator there too? p.newEyes = new Eyes; #-o

    And btW, although it works, we shouldn't prefix our own stuff w/ p5's parameter p. :-@
    Reserve it for p5.js API properties only: const newEyes = new Eyes; L-)

  • edited January 2017

    Another advise. Avoid plural names for classes.
    Rather than calling your class there Eyes, rename it as EyePair for example. *-:)

  • edited January 2017

    1 more advise: JS got keyword class already: >-)
    https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

    So your class EyePair can be implemented as below instead: :ar!

    class EyePair {
      constructor(left = [10, 50], right = [50, 50]) {
        this.left = left, this.right = right;
      }
    }
    

    However, for instance mode, we need to move all classes to the top, b/c declaring keyword class doesn't hoist like function does! :-S

  • edited January 2017

    Oh, I thought 'function ObjectName(arg1,...) { ... }' was the way to write a class constructor in javascript. I'll try to use your syntax lol, I always wondered why the javascript constructor looked weird. Also thanks for the style advice.

    As for using 'new', I didn't use it because I wanted my second object to inherit some of the same variables from the first object. Maybe my problem would be clearer if I had something like:

    class EyePair(p5object) {
        constructor(left = [p5object.mouseX, 50], right = [p5object.mouseY, 50]) {
             this.left = left, this.right = right;
        }
    }
    

    and then constructed it in sketch1 using the syntax:

    var sketch = function(p) {
       var eyePair = new EyePair(main);
       //etc...
    }
    

    When I create the object in sketch2, I want it to take the numeric values of this.left and this.right from the first object, not create new this.left/this.right values based on my current mouse coordinates. And furthermore, after 'copying' the this.left/this.right values, I want to be able to modify them in the new object without affecting the old object.

  • edited January 2017 Answer ✓
    • Let's start w/ your EyePair class 1st.
    • What you're looking for is deep cloning.
    • That is, you want another instance of the class, but initializing it w/ the same properties of some previous 1.
    • In order to streamline it, let's create a method called clone() for the class.

    class EyePair {
      constructor(left, right) {
        this.left = left, this.right = right;
      }
    
      clone() {
        return new EyePair(this.left, this.right);
      }
    }
    

    Now we can test it w/ these lines below in any JS console:

    var q = new EyePair([5, 4], [2, -4]);
    var w = q.clone();
    
    w.right[1] = -25;
    console.log(w.right[1], q.right[1]); // -25 -25 !?
    

    However as you can see, the clone() method has completely failed! @-)
    The cloned EyePair's arrays are still affecting the original EyePair! 3:-O

    The reason why is b/c at return new EyePair(this.left, this.right);, the very same arrays are being passed to the new instance. It's still a shallow cloning instead of a deep 1.

    You know, variables store an object's 1st memory address (a.K.a. pointer or reference). :-B
    So both instances got their properties pointing at the same memory addresses.
    If we modify any of the arrays' index from either instance, the same will happen to the other! :-\"

    In order to fix that, we've gotta clone the arrays themselves as well.
    There are many ways to do that in JS. But I've selected 3 below:

    1. Array::slice(): https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
    2. Array.from(): https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
    3. Operator spread ...: https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

    class EyePair {
      constructor(left, right) {
        this.left = left, this.right = right;
      }
    
      clone() {
        return new EyePair(this.left.slice(), this.right.slice());
        //return new EyePair(Array.from(this.left), Array.from(this.right));
        //return new EyePair([...this.left], [...this.right]);
      }
    }
    

    Now let's do the test again, shall we? ;;)

    var q = new EyePair([5, 4], [2, -4]);
    var w = q.clone();
    
    w.right[1] = -25;
    console.log(w.right[1], q.right[1]); // -25 -4 Yay!
    

    Now it's working. Modifying any of the cloned EyePair's arrays won't affect the original 1s! <:-P

Sign In or Register to comment.