Antti Keränen
Antti Keränen

Reputation: 45

Why does the same value get written 5 times to the array?

var flagArray = [];
var flagTarget = document.getElementById("content");

var flag = {
  init: function(country) {
    if (country) {
      this.country = country;
      flagArray.push(Object.create(flag));
    }
  },
  draw: function() {
    var pos = flagArray.indexOf(this.country);
    if (this.country === "norway") {
      flagArray[pos] = '<div class="flag ' + this.country + '"><div class="part1"></div><div class="part2"></div><div class="part3"></div><div class="part4"></div></div>';
    } else {
      flagArray[pos] = '<div class="flag ' + this.country + '"><div class="part1"></div><div class="part2"></div></div>';
    }
    flagTarget.innerHTML += flagArray[pos];
  },
};

flag.init("ivorycoast");
flag.init("sweden");
flag.init("denmark");
flag.init("finland");
flag.init("norway");

flagArray.forEach(function(flag) {
  flag.draw();
});

I've been struggling with this school assignment for too many hours now. Why does the last flag.init value get written 5 times to the array?

Upvotes: 0

Views: 158

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1073998

It doesn't, but it seems like it does.

What's happening is that you're updating country on flag five times, and each time you're creating a new object that inherits from flag and pushing that on the array. So the end result is five different objects in the array, all of which inherit from flag, and none of which have their own country value. So the country you see on them is the one inherited from flag, which is the last value you assigned to this.country.

If you want them to inherit from flag but each have their own country value, you need to remember the object created with Object.create and set country on it:

init: function(country) {
  if (country) {
    var newFlag = Object.create(flag);
    flagArray.push(newFlag);
    newFlag.country = country;
  }
},

(There's a more advanced way I show after the following diagrams.)

Side note: There's a separate problem in draw, this line:

var pos = flagArray.indexOf(this.country);

should just be

var pos = flagArray.indexOf(this);

Because the array contains objects, not country strings. That said, you don't need to use flagArray in draw at all.

Let's throw some ASCII-Art (actually, Unicode-art) at your current code to understand what you're seeing:

You start out with this in memory:

                                                         +−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−>|       (object)        |
                                                         +−−−−−−−−−−−−−−−−−−−−−−−+
                                                         | init: (function)      |
                                                         | draw: (function)      |
                                                         +−−−−−−−−−−−−−−−−−−−−−−−+

             +−−−−−−−−−−−+
flagArray−−−>|  (array)  |
             +−−−−−−−−−−−+
             | length: 0 |
             +−−−−−−−−−−−+

Then after the first init call, you have:

                                                         +−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−−−−−>|       (object)        |
                                                 |       +−−−−−−−−−−−−−−−−−−−−−−−+
                                                 |       | init: (function)      |
                                                 |       | draw: (function)      |
                                                 |       | country: "ivorycoast" |
                                                 |       +−−−−−−−−−−−−−−−−−−−−−−−+
                                                 |
             +−−−−−−−−−−−+                       |
flagArray−−−>|  (array)  |                       |
             +−−−−−−−−−−−+                       |
             | length: 1 |    +−−−−−−−−−−−−−−−+  |
             | 0         |−−−>|    (object)   |  |
             +−−−−−−−−−−−+    +−−−−−−−−−−−−−−−+  |
                              | [[Prototype]] |−−+
                              +−−−−−−−−−−−−−−−+

You've created a new object with flag as its prototype, and set flag's country to "ivorycoast".

After the second call to init, you have:

                                                         +−−−−−−−−−−−−−−−−−−−−−−−+
flag−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−+−−−−>|       (object)        |
                                                 | |     +−−−−−−−−−−−−−−−−−−−−−−−+
                                                 | |     | init: (function)      |
                                                 | |     | draw: (function)      |
                                                 | |     | country: "sweden"     |
                                                 | |     +−−−−−−−−−−−−−−−−−−−−−−−+
                                                 | |
             +−−−−−−−−−−−+                       | |
flagArray−−−>|  (array)  |                       | |
             +−−−−−−−−−−−+                       | |
             | length: 2 |    +−−−−−−−−−−−−−−−+  | |
             | 0         |−−−>|    (object)   |  | |
             | 1         |−+  +−−−−−−−−−−−−−−−+  | |
             +−−−−−−−−−−−+ |  | [[Prototype]] |−−+ |
                           |  +−−−−−−−−−−−−−−−+    |
                           |                       |
                           |   +−−−−−−−−−−−−−−−+   |
                           +−−>|    (object)   |   |
                               +−−−−−−−−−−−−−−−+   |
                               | [[Prototype]] |−−−+
                               +−−−−−−−−−−−−−−−+

...and so on, until flagArray is filled with five separate objects, all using flag as their prototype, and flag has "norway" for its country property.


Here's that more advanced way to add country that I mentioned. If I were writing production code, I wouldn't use it for this (too verbose), but just for completeness: There's a second argument you can give Object.create that can have a list of properties to add to the new object. You specify the properties by giving their name and an object the specifies the various aspects of the property, like whether it's enumerable, whether it's read-only or writable, its initial value (or a getter and/or setter), etc. The writable, enumerable, and configurable features of a property are all defaulted false when you do this, but they default true when you create a property by just assigning to it. So to do the same thing we're doing with newFlag.country = country, we have to specify all those things and say true to make the property the same.

So, this does the same thing the version earlier using newFlag did, just without the temporary variable:

init: function(country) {
  if (country) {
    flagArray.push(Object.create(flag, {
        country: {
            enumerable: true,
            configurable: true,
            writable: true,
            value: country
        }
    }));
  }
},

Upvotes: 5

pawel
pawel

Reputation: 36965

Using Object.create(flag) you set the flag as prototype of each element in the array. The prototype is shared between all objects, so this.country gets updated on the prototype object in each init call and inherited by every element in the array.

To create a separate object you should try Object.assign, for example:

flagArray.push(Object.assign(Object.create(flag), { country : country }));

https://jsfiddle.net/p58189f1/

Upvotes: 1

Related Questions