Skip to content Skip to sidebar Skip to footer

Objects Contain Same Values But Shouldn't

I try to achieve an object inheritance, where each object shares the same functionality but contains different values/containers. Doing so I discovered a strange behavior; each ind

Solution 1:

objects contain same values but shouldn't

They should, because you haven't changed the options property on variation, so it still points at the same object that the original.options points to.

When you do this:

var original = {
    options: {
        value: null,
        factor: 13
    },

    init: function (container) {
        this.options.value = container.getAttribute('data-value');
    }
};

Here's what you get in memory (some details omitted):

                                  +−−−−−−−−−−−−−−−−−−−−+
            +−−−−−−−−−−−−+   +−−−>| (Object.prototype) |<−+
original−−−>|  (object)  |   |    +−−−−−−−−−−−−−−−−−−−−+  |
            +−−−−−−−−−−−−+   |                            |
            | __proto__  |−−−+    +−−−−−−−−−−−−−+         |
            | options    |−−−−−−−>|  (object)   |         |
            | init       |−−−+    +−−−−−−−−−−−−−+         |
            +−−−−−−−−−−−−+   |    | __proto__   |−−−−−−−−−+
                             |    | value: null |
                             |    | factor: 13  |
                             |    +−−−−−−−−−−−−−+
                             |
                             |    +−−−−−−−−−−−−+
                             +−−−−| (function) |
                                  +−−−−−−−−−−−−+

...where __proto__ is a pseudo-property showing what the object's prototype is (specifically, the value of what the spec calls the object's internal [[Proto]] property). (On browsers, in ES2015, there is actually a __proto__ accessor, but it shouldn't be used.)

Then when you do this:

var variation = Object.create(original);

You have:

            +−−−−−−−−−−−−+
variation−−>|  (object)  |
            +−−−−−−−−−−−−+
            | __proto__  |−−+
            +−−−−−−−−−−−−+  |
                            |                              
         +−−−−−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+   
         |                     +−>| (Object.prototype) |<−+
         \  +−−−−−−−−−−−−+     |  +−−−−−−−−−−−−−−−−−−−−+  |
original−−−>|  (object)  |     |                          |
            +−−−−−−−−−−−−+     |                          |
            | __proto__  |−−−−−+  +−−−−−−−−−−−−−+         |
            | options    |−−−−−−−>|  (object)   |         |
            | init       |−−−+    +−−−−−−−−−−−−−+         |
            +−−−−−−−−−−−−+   |    | __proto__   |−−−−−−−−−+
                             |    | value: null |
                             |    | factor: 13  |
                             |    +−−−−−−−−−−−−−+
                             |
                             |    +−−−−−−−−−−−−+
                             +−−−−| (function) |
                                  +−−−−−−−−−−−−+

You can see how both original and variation are still pointing at the same options object.

If you want separate options objects, you have to create a new object. You could make variation.options use original.options as its prototype:

var variation = Object.create(original);
variation.options = Object.create(original.options);

...but then you'd have to do it again for firstDiv and secondDiv:

var firstDiv = Object.create(variation);
firstDiv.options = Object.create(variation.options);
var secondDiv = Object.create(variation);
secondDiv.options = Object.create(variation.options);

...which suggests we need to do something different.

You could have a function you use to do it:

functioncreateVariation(source) {
    var v = Object.create(source);
    v.options = Object.create(source.options);
    return v;
}

var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);

...but you might look at constructor functions or maker functions, and a fairly typical pattern called the "extend" function (such as jQuery's or Underscore's).

Solution 2:

Neither firstDiv nor secondDiv has its own options property. Both inherit options from original. You can test this by observing that all of the expressions below are false:

variation.hasOwnProperty("options") // false
firstDiv.hasOwnProperty("options")  // false
secondDiv.hasOwnProperty("options") // false

Your init function does this.options.value = ..., but consider for a moment only the expression this.options. You never create another options object, so when you ask for firstDiv or secondDiv for its options object, that escalates the this.options lookup to the original prototype object.

Thus, when you access the options property of either firstDiv or secondDiv, you are causing the property lookup to escalate to original (the first object in the prototype chain that really has an options property). This means that firstDiv.options and secondDiv.options are the exact same object (namely, original.options). Therefore, it follows that when you set secondDiv.options.value, you are also setting firstDiv.options.value.

The solution here is to ensure that firstDiv and secondDiv explicitly have their own options property. However, you also want that options value to itself inherit from variation.options, and you want variation to have its own options value as well, which inherits from original.options. T.J. Crowder's answer addresses this in detail with his createVariation method:

functioncreateVariation(source) {
    var v = Object.create(source);
    v.options = Object.create(source.options);
    return v;
}

var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);

Post a Comment for "Objects Contain Same Values But Shouldn't"