2

I have an issue that I am struggling to grasp. Any help would be greatly appreciated.

I have an Object, and I assign the current object state to a property on the current object.

example below:

var product = {
  ropeType: 'blah',
  ropePrice: 'blah',
  ropeSections: {
    name: 'blaah',
    price: 'blaah'
  },
  memory: false
}

product.memory = product;

Now when I look at the product object within the console I get a inifinite recursion of Product.memory.Product.memory.Product....

screenshot below:

enter image description here

I know its something to do with that an object references itself, but I cannot seem to grasp the concept. Could someone explain?

The reason I am trying to do something like this is to save in local storage the current state of the object.

I hope I have made sense.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Adamski
  • 839
  • 2
  • 10
  • 28
  • What exactly is it that you don't understand? It's a cyclic data structure. Think about a circular path. If walk along the path you eventually end up where you started. – Felix Kling Jun 25 '15 at 22:16
  • Use a different variable to store the object. If you attach an object to a property of itself, it's going to be infinitely self-referential. – Michael Todd Jun 25 '15 at 22:17
  • Oh ok, so objects are always self referencing? – Adamski Jun 25 '15 at 22:21
  • Uhm, no. But objects are represented through references. `product.memory = product;` assigns a reference to `product` to itself. – Felix Kling Jun 25 '15 at 22:22

4 Answers4

4

I assign the current object state to a property on the current object.

No, you created a property that referred to itself.

If you want to save the current state of the property then you need to clone the object.

If you want to create a (shallow) copy of an object then you can use:

function clone(obj) {
    if(obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    var temp = obj.constructor();

    for(var key in obj) {
        if(Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = obj[key];
            delete obj['isActiveClone'];
        }
    }    

    return temp;
}

[code taken from here - and modified slightly to do a shallow copy rather than recursive deep copy]

then do:

product.memory = clone( product );

You may find you get the issues with recursion if you clone it a second time and it copies the product.memory along with the rest of the object. In that case just delete product.memory before doing subsequent clones.

Something like:

function saveCurrentState( obj ){
  if ( 'memory' in obj )
    delete obj.memory;
   obj.memory = clone( obj );
}

Aside

If you want a deep copy then you can do:

function deepCopy(obj){
  return JSON.parse(JSON.stringify(obj));
}

[As suggested here - but note the caveats it has for Date objects]

Community
  • 1
  • 1
MT0
  • 143,790
  • 11
  • 59
  • 117
1

This here is the problem:

product.memory = product;

You're assigning a reference to an object to itself. JavaScript passes objects by reference, so it's never going to store a clone of itself through assignment.

If you're looking to record modifications made to the object over time, the best way would be to use an array to hold cloned copies of it (or at least the properties that've changed).

To give you the quickest example:

var Product    =    function(){

};

var product       =    new Product();
product.history   = [];
product.saveState = function(){
    var changes = {};

    for(var i in this){

        /** Prevent infinite self-referencing, and don't store this function itself. */
        if(this[i] !== this.history && this[i] !== this.saveState){
            changes[i] = this[i];
        }
    }

    this.history.push(changes);
};

Obviously, there're many better ways to achieve this in JavaScript, but they require more explanation. Basically, looping through an object to store its properties is inevitably going to trip up upon the property that they're being assigned to, so a check is needed at some point to prevent self-referencing.

  • Ok thanks, How would you achieve what I am trying to do? – Adamski Jun 25 '15 at 22:26
  • Done. Bear in mind this isn't the best solution, but it's possibly the easiest to explain if you're not yet familiar with how property assignment and referencing works in JavaScript. –  Jun 25 '15 at 22:39
1

you could do your idea by clone the current product into new. We've Object.keys to get all attribute of object. So here is my idea :

product = {
   ropeType: 'blah',
   ropePrice: 'blah',
   ropeSections: {
      name: 'blaah',
      price: 'blaah'
   },
   memory: false
};
var keys = Object.keys(product);
var newProduct = {};
keys.forEach(function(key){
   if(key === 'memory') return;
   newProduct[key] = product[key];
});
product.memory = newProduct;
Kai
  • 3,104
  • 2
  • 19
  • 30
  • 1
    [Object.keys()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#Browser_compatibility) is not supported by all browsers - i.e. IE8 and earlier amongst other older version of browsers. – MT0 Jun 25 '15 at 22:38
  • he asked for ES5 only, so I thought it would be work well :). – Kai Jun 25 '15 at 22:44
1

Instead of actually storing a reference to the object, you might want to transform that object's state. Maybe by cloning it onto a new object or possibly keeping it as a JSON string (which you'll want to do if you're using localStorage).

Since you will probably want to see the current state of the object whenever you check the memory property, you should make memory a function that does that transformation.

Maybe something like this:

var product = {
  ropeType: 'blah',
  ropePrice: 'blah',
  ropeSections: {
    name: 'blaah',
    price: 'blaah'
  },
  memory: function() {     
    return JSON.stringify(this);
  }
}

You can then call product.memory() and get its state in JSON.

Daniel Weiner
  • 1,874
  • 12
  • 19