Welcome to the prototype chain!
Let's see what it looks like in your example.
The Problem
When you call new Thing()
, you are creating a new object with a property relatedThings
which refers to an array. So we can say we have this:
+--------------+
|Thing instance|
| |
| relatedThings|----> Array
+--------------+
You are then assigning this instance to ThingA.prototype
:
+--------------+
| ThingA | +--------------+
| | |Thing instance|
| prototype |----> | |
+--------------+ | relatedThings|----> Array
+--------------+
So each instance of ThingA
will inherit from the Thing
instance. Now you are going to create ThingA1
and ThingA2
and assign a new ThingA
instance to each of their prototypes, and later create instances of ThingA1
and ThingA2
(and ThingA
and Thing
, but not shown here).
The relationship is now this (__proto__
is an internal property, connecting an object with its prototype):
+-------------+
| ThingA |
| |
+-------------+ | prototype |----+
| ThingA1 | +-------------+ |
| | |
| prototype |---> +--------------+ |
+-------------+ | ThingA | |
| instance (1) | |
| | |
+-------------+ | __proto__ |--------------+
| ThingA1 | +--------------+ |
| instance | ^ |
| | | v
| __proto__ |-----------+ +--------------+
+-------------+ |Thing instance|
| |
| relatedThings|---> Array
+-------------+ +--------------+ +--------------+
| ThingA2 | | ThingA | ^
| | | instance (2) | |
| prototype |---> | | |
+-------------+ | __proto__ |--------------+
+--------------+
+-------------+ ^
| ThingA2 | |
| instance | |
| | |
| __proto__ |-----------+
+-------------+
And because of that, every instance of ThingA
, ThingA1
or ThingA2
refers to one and the same array instance.
This is not what you want!
The Solution
To solve this problem, each instance of any "subclass" should have its own relatedThings
property. You can achieve this by calling the parent constructor in each child constructor, similar to calling super()
in other languages:
function ThingA() {
Thing.call(this);
}
function ThingA1() {
ThingA.call(this);
}
// ...
This calls Thing
and ThingA
and sets this
inside those function to the first argument you pass to .call
. Learn more about .call
[MDN] and this
[MDN].
This alone will change the above picture to:
+-------------+
| ThingA |
| |
+-------------+ | prototype |----+
| ThingA1 | +-------------+ |
| | |
| prototype |---> +--------------+ |
+-------------+ | ThingA | |
| instance (1) | |
| | |
| relatedThings|---> Array |
+-------------+ | __proto__ |--------------+
| ThingA1 | +--------------+ |
| instance | ^ |
| | | |
|relatedThings|---> Array | v
| __proto__ |-----------+ +--------------+
+-------------+ |Thing instance|
| |
| relatedThings|---> Array
+-------------+ +--------------+ +--------------+
| ThingA2 | | ThingA | ^
| | | instance (2) | |
| prototype |---> | | |
+-------------+ | relatedThings|---> Array |
| __proto__ |--------------+
+--------------+
+-------------+ ^
| ThingA2 | |
| instance | |
| | |
|relatedThings|---> Array |
| __proto__ |-----------+
+-------------+
As you can see, each instance has its own relatedThings
property, which refers to a different array instance. There are still relatedThings
properties in the prototype chain, but they are all shadowed by the instance property.
Better Inheritance
Also, don't set the prototype with:
ThingA.prototype = new Thing();
You actually don't want to create a new Thing
instance here. What would happen if Thing
expected arguments? Which one would you pass? What if calling the Thing
constructor has side effects?
What you actually want is to hook up Thing.prototype
into the prototype chain. You can do this with Object.create
[MDN]:
ThingA.prototype = Object.create(Thing.prototype);
Anything that happens when the constructor (Thing
) is executed will happen later, when we actually create a new ThingA
instance (by calling Thing.call(this)
as shown above).