8

I'm trying to use inheritance in JavaScript here, and I found problem having array value in Parent class that is inherited by a Child class. Below code is normal inheritance:

var Parent = function() {
    this.list = [];
};

var Child = function() {};

Child.prototype = new Parent;
Child.prototype.constructor = Child;

var obj1 = new Child;

obj1.list.push("hello");

console.log(obj1.list); // prints ["hello"];

When I initialized new Child object (inherits Parent which contains array variable named list) to obj1 and tried to push obj1.list with a value "hello", obj1.list prints ["hello"] .. so far so good.

The problem comes when I did the above example and I tried to initialize new Child object to obj2 then push obj2's list with a value "goodbye", and obj2.list now prints ["hello", "goodbye"]. (See the code below:)

var obj2 = new Child;

obj2.list.push("goodbye");

console.log(obj2.list); // prints ["hello", "goodbye"];

I might have a misconception here, but the list array in Parent is somehow retaining the value and I don't know why.

This is a big trouble because if I reuse the Parent class to many other child classes like the case above, if Parent has array variable shared to its child classes, the value will be shared to the other child classes as well, which is unexpected for me.

What I expected is, the Child class represents new object, same goes to Parent class when Child class is initialized to obj1, which then when I initialize new Child object to obj2, the pushed value from obj1 should not be shared with obj2.

-- Question --

Can anyone help me find out why the list (the array variable of Parent) in the example above retains the values/share the values that are initiated by the Child objects (in above case, obj1 and obj2)?

If you have another solution that could solve this problem, it will be very nice of you, but I'll be happy to find out the problem above first.

  • Because both instances refer to the same `Parent` instance as their prototype and therefore to the same array. There have been many JavaScript OOP questions here, you should have a look at them. – Felix Kling Feb 16 '12 at 12:12
  • possible duplicate of [Undesrtanding Javascript's OOP: instances modifying other instances](http://stackoverflow.com/questions/4541788/undesrtanding-javascripts-oop-instances-modifying-other-instances) and [Javascript prototype property not working as expected with array and object fields](http://stackoverflow.com/questions/8230085/javascript-prototype-property-not-working-as-expected-with-array-and-object-fiel) – Felix Kling Feb 16 '12 at 12:16

2 Answers2

14

When a child object has a property inherited from its prototype object, what's really happening is that the child has a reference to the prototype, which contains the property. The child doesn't have its own copy of it. So both children are using the same array — the one on the (one) Parent prototype object you've assigned to Child.prototype.

First some pictures, then more text. :-)

new Parent() gives you this:

+-----------------+
| Parent instance |
+-----------------+
| list = []       |
+-----------------+

...which you then assign to Child.prototype.

Then, new Child() gives you this:

+------------------+
| Child instance 1 |
+------------------+        +-----------------+
| (prototype)      |------->| Parent instance |
+------------------+        +-----------------+
                            | list = []       |
                            +-----------------+

Doing new Child() again gives you another one:

+------------------+      +------------------+
| Child instance 1 |      | Child instance 2 |
+------------------+      +------------------+
| (prototype)      |---+  | (prototype)      |---+
+------------------+   |  +------------------+   |
                       |                         |
                       |                         |     +-----------------+
                       +-------------------------+---->| Parent instance |
                                                       +-----------------+
                                                       | list = []       |
                                                       +-----------------+

So as you can see, all of the Child instances are sharing the same Parent instance (their prototype), which has an array on it.

When you say:

var obj = new Child();
obj.list.push("foo");

...here's what the JavaScript engine does when it sees obj.list:

  1. Looks to see if obj has its own property called list. If not, then:
  2. Looks to see if obj's prototype has its own property called list. If not, then:
  3. Looks to see if obj's prototype's prototype has its own property called list.
  4. Etc. until we run out of prototypes.

In your case, since obj doesn't have its own list property, the engine looks to its prototype (the Parent instance you assigned to Child.prototype), which in this case does have the property. So that one is used. It's not copied to the child or anything, it's used. And of course, since it's an array, pushing something on the array actually pushes it onto the array.

If you were to assign something to obj.list, then obj would get its own list property, breaking the chain. So putting this.list = []; in Child would give each child its own list.

You'll see this any time a prototype has an object reference on it, where the object is a type that can be modified (a "mutable" object). Arrays, Dates, plain objects ({}), RegExps, etc., they all have state and they can all be modified, so you'd see it with them. (String instances are immutable, so although this same thing happens, you don't see any effect from it because the string cannot be changed.)

With primitives, although you inherit them, you can't change their state (you can only replace them with a new primitive with a different state), so you don't see this same effect. So if obj inherits the property foo from its prototype, and foo is 42, alert(obj.foo) will get the value from the prototype and show "42". The only way to change foo is to say obj.foo = 67 or similar — which gives obj its own copy of foo, distinct from the prototype's copy. (This is true even if you use things like ++ and --, e.g. ++obj.foo; that really gets evaluated as obj.foo = obj.foo + 1).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 5
    Your awesome ascii graphics warrant more than +1, but that's all I'm allowed to reward – nikc.org Feb 16 '12 at 12:19
  • 1
    Super awesome explanation. Thank Crowder, not only you define me why's list in my code example is actually a reference, but now I understand how the prototype works in your 1,2,3,4 points. :) (oh .. and nice ASCII-art) –  Feb 16 '12 at 12:27
3

The thing you're missing here, is that Parent.list is being instantiated only once; when you copy its' prototype into Child.prototype.

Instantiating a "subclass" in JavaScript does not automatically invoke the parent constructor. Thus, all instances of Child will share the same instance of the array.

Modifying the constructor of Child to invoke the parent constructor should be the cure you seek for your ailment:

var Child = function() {
    Parent.call(this);
};

Obviously you already have some knowledge on the subject, but if you're interested, here's a few texts on the topic worth a read:

nikc.org
  • 16,462
  • 6
  • 50
  • 83
  • Thanks nikc! honestly, your sample code is the one that actually solve my latest problem this second. I forgot to use .call that leads me to this question. –  Feb 16 '12 at 12:30