I'm new to Javascript programming and I'm approaching my first application (a game, indeed) from an object oriented programming perspective (I know js is not really object oriented, but for this particular problem it was easier for me to start like this).
I have a hierarchy of "classes" where the top-most ("Thing" class) defines a list of related things (attached items in the game). It's inherited by a ThingA class which is inherited by ThingA1 and ThingA2 classes.
The minimal example would be:
function Thing()
{
this.relatedThings = [];
}
Thing.prototype.relateThing = function(what)
{
this.relatedThings.push(what);
}
ThingA.prototype = new Thing();
ThingA.prototype.constructor = ThingA;
function ThingA()
{
}
ThingA1.prototype = new ThingA();
ThingA1.prototype.constructor = ThingA1;
function ThingA1()
{
}
ThingA2.prototype = new ThingA();
ThingA2.prototype.constructor = ThingA2;
function ThingA2()
{
}
var thingList = [];
thingList.push(new ThingA());
thingList.push(new ThingA1());
thingList.push(new ThingA2());
thingList.push(new ThingA2());
thingList.push(new Thing());
thingList[1].relateThing('hello');
At the end of the code, when the relateThing is executed, every ThingA, ThingA1 and ThingA2 is going to execute it (not the last "Thing" object in the array). I've found if I define the relateThing function in the ThingA prototype, it will work right. Because of how the game is designed I'll prefer not to have to do that.
Maybe I'm not understanding something about how the prototypes work in javascript. I know the function is shared among all the objects, but i guess the execution would be individual. Could somebody explain why is this happening and how to solve it? I don't know if I'm doing the inheritance wrong, or the prototypes definitions, or what.
Thanks in advance.
An object can be inherited from multiple other objects, i.e. the object has common property from other parent objects not only a single parent. In JavaScript, this can be achieved by merging properties from different properties into one single object.
When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.
The multi-level inheritance includes the involvement of at least two or more than two classes. One class inherits the features from a parent class and the newly created sub-class becomes the base class for another new class.
Details. In object-oriented programming (OOP), inheritance describes a relationship between two classes in which one class (the child class) subclasses the parent class. The child inherits methods and attributes of the parent, allowing for shared functionality.
Welcome to the prototype chain!
Let's see what it looks like in your example.
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!
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.
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).
If you don't like the way prototyping works in JavaScript in order to achieve a simple way of inheritance and OOP, I'd suggest taking a look at this: https://github.com/haroldiedema/joii
It basically allows you to do the following (and more):
// First (bottom level)
var Person = new Class(function() {
this.name = "Unknown Person";
});
// Employee, extend on Person & apply the Role property.
var Employee = new Class({ extends: Person }, function() {
this.name = 'Unknown Employee';
this.role = 'Employee';
});
// 3rd level, extend on Employee. Modify existing properties.
var Manager = new Class({ extends: Employee }, function() {
// Overwrite the value of 'role'.
this.role = 'Manager';
// Class constructor to apply the given 'name' value.
this.__construct = function(name) {
this.name = name;
}
});
// And to use the final result:
var myManager = new Manager("John Smith");
console.log( myManager.name ); // John Smith
console.log( myManager.role ); // Manager
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With