0

I'm trying to animate Objects in a Canvas with a function, so I wrote this object - which I'm trying to animate:

var chartElements = {
    'bottomLine': {
        'width': 0,
        setWidth: function (x) {
            this.width = x;
        },
        getWidth: function () {
            return this.width;
        },
    },
};

...and created it with: var cE = Object.create(chartElements); So my, first thought was, to just change the var with cE.chartElements.bottomLine.width = 10, but this didn't work so I wrote a function where I can reference to the getters and setters:

function changeIt(val, getter, setter) {
    console.log(getter());
    setter(val);
    console.log(getter());
}

And now it's getting very, very weird because first, the getter-Function returnes an undefined, but after it's set, it always returnes the new variable but without changing the actual property of the cE-Object. So it seems like I'm creating a new Object somewhere.

So, whats wrong, why can't I change the actual property of cE?

JulianWels
  • 243
  • 3
  • 12

1 Answers1

3

There are two problems here. The first problem is that you're trying to use a property on your object (chartElements) that doesn't exist. Instead of

cE.chartElements.bottomLine.width = 10

it would be

cE.bottomLine.width = 10;

or, of course, using the setter you created:

cE.bottomLine.setWidth(10);

but, that brings us to the second problem:

By doing

var cE = Object.create(chartElements);

...you're creating an object, referred to by cE, that uses chartElements as its underlying prototype. In memory, you get something that looks like this (love me some ASCII-art):

                         +-------------+                                  
chartElements--------+-->|   (object)  |                                  
                     |   +-------------+      +----------+               
                     |   | bottomLine  |----->| (object) |               
                     |   +-------------+      +----------+               
                     |                        | width: 0 |               
      +-----------+  |                        | setWidth |---->(function)
cE--->| (object)  |  |                        | getWidth |---->(function)
      +-----------+  |                        +----------+               
      | __proto__ |--+
      +-----------+

That means that cE.bottomLine refers to the prototype's bottomLine property. If you change it, you'll be changing the bottomLine object of the prototype, which would mean if you have other objects you've created via Object.create(chartElements), if they use bottomLine, they'll see the change (because they're all using the same bottomLine).

Instead, you might have a prototype for bottomLine:

var bottomLinePrototype = {
    width: 0,
    setWidth: function (x) {
        this.width = x;
    },
    getWidth: function () {
        return this.width;
    }
};

and a constructor function or builder function for your chart element creation:

Here's a constructor function:

// Constructor function
function ChartElement() {
    this.bottomLine = Object.create(bottomLinePrototype);
}

// use it via `new`
var cE = new ChartElement();

Here's a builder function:

// Builder function
function buildChartElement() {
    return {
        bottomLine: Object.create(bottomLinePrototype)
    };
}

// Use it *without* `new`
var cE = buildChartElement();

Now, your objects have their own bottomLine object:

      +-------------+      
cE--->|   (object)  |      
      +-------------+      +-----------+
      | bottomLine  |----->| (object)  |
      +-------------+      +-----------+     +----------+               
                           | __proto__ |---->| (object) |               
                           +-----------+     +----------+               
                                             | width: 0 |               
                                             | setWidth |---->(function)
                                             | getWidth |---->(function)
                                             +----------+               

Initially that looks pretty similar, but the difference is that since the object has its own bottomLine object, you can safely use:

cE.bottomLine.width = 10;

or

cE.bottomLine.setWidth(10);

...to set that instance's bottomLine's width, which will shadow the one on its prototype, giving us:

      +-------------+      
cE--->|   (object)  |      
      +-------------+      +-----------+
      | bottomLine  |----->| (object)  |
      +-------------+      +-----------+     +----------+               
                           | __proto__ |---->| (object) |               
                           | width: 10 |     +----------+               
                           +-----------+     | width: 0 |               
                                             | setWidth |---->(function)
                                             | getWidth |---->(function)
                                             +----------+               

This works because prototype properties work differently when you're doing a set vs. get operation:

  • When you're getting, if the object itself doesn't have the property, the JavaScript engine looks to the object's prototype to see if it has the property. If so, it uses the value of the prototype's property. If not, it looks to the prototype's prototype, and so on.

  • When you're setting, though, it sets the value on the object itself, creating a new property on the object if necessary or upating the value of the property if it's already there. The prototype's property is unaffected.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Nice explanation of the problem :-) Should be linked to [here](https://stackoverflow.com/questions/10131052/crockfords-prototypal-inheritance-issues-with-nested-objects/) and [there](https://stackoverflow.com/questions/4425318/javascript-object-members-that-are-prototyped-as-arrays-become-shared-by-all-cla) – Bergi May 03 '15 at 12:56