JS Inheritance contra Encapsulation


Published: 2014-10-16
Updated: 2017-12-02
Web: https://fritzthecat-blog.blogspot.com/2014/10/js-prototype-inheritance-contra.html


Object-oriented languages have following features:

There are other less shortly explainable features, but lets sum up now for JavaScript.

I would say, three of seven.

"Of course, JS is object-oriented, isn't everything in JS an object?"
"Yes", I answered, "and its functional too, everywhere you see functions ..."

The following is a result of my studies about JS inheritance, but it is nothing I can recommend, and you won't find a working inheritance solution here. You could skip this and read about functional inheritance, which is what I would advice.

Inheritance (pseudo-classical? prototypal?)

The only thing that is sure about JavaScript is that it is very flexible. So some kind of inheritance must be there, right?
If you search the web for JavaScript inheritance examples, you find things like this:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Animal = function(species) {
this.species = species;
this.food = "none";

this.eat = function(givenFood) {
this.food = givenFood;
};
this.toString = function() {
return this.species+" ("+this.food+")";
};
};

var Cat = function(name) {
this.name = name;

this.toString = function() {
var superToString = Cat.prototype.toString.call(this); // super.toString()
return this.name+" <"+superToString+">";
};
};
Cat.prototype = new Animal("FourLegs");
Cat.prototype.constructor = Cat;

Animal and Cat are called constructor functions, and thus have capitalized names.
(You should never call such a function without the new operator, because such could cause unintended global variables.)

In Cat we have a super-object call which looks (and is) a little tedious:

Cat.prototype.toString.call(this);
Translated this reads as "take the overwritten function toString() from Cat's prototype object and execute it as if it was bound to 'this' Cat".

The line

Cat.prototype = new Animal("FourLegs");
is the "inheritance installation", meaning this is what you are supposed to do when you want an object to inherit from another (as there are no classes, you need objects that you can inherit from).

The line

Cat.prototype.constructor = Cat;
is for a proper constructor function, so that calling a Cat's constructor would create a Cat instance and not an Animal instance.

Here is some test code:

1
2
3
4
5
6
7
8
var garfield = new Cat("Garfield");
console.log("Garfield = "+garfield);
var catbert = new Cat("Catbert");
console.log("Catbert = "+catbert);
garfield.eat("mouse");
catbert.eat("cheese");
console.log("Garfield after meal = "+garfield); // has he cheese?
console.log("Catbert after meal = "+catbert);

This code outputs:


Garfield = Garfield <FourLegs (none)>
Catbert = Catbert <FourLegs (none)>
Garfield after meal = Garfield <FourLegs (mouse)>
Catbert after meal = Catbert <FourLegs (cheese)>

What is in (parentheses) is what the cat has in its belly.
We need to make sure that Catbert has nothing in its belly that Garfield has eaten ;-)

Encapsulation

No problem until now, except that everything is public. Now I bring in what I would call a "private variable":

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Animal = function(species) {
this.species = species;

this.food = "none";
var privFood = "empty"; // private

this.eat = function(givenFood) {
this.food = givenFood; // write to public field
privFood = givenFood; // write to private field
};
this.toString = function() {
return species+" ("+this.food+" / "+privFood+")";
};
};

My expectation is that every instance of Cat has its own private privFood variable. The public food variable is for checking whether that assumption holds.
Running the same test lines as above, guess what's the result!


Garfield = Garfield <FourLegs (none / empty)>
Catbert = Catbert <FourLegs (none / empty)>
Garfield after meal = Garfield <FourLegs (mouse / cheese)>
Catbert after meal = Catbert <FourLegs (cheese / cheese)>

How can Garfield have "cheese" as its privFood variable?
When you consider that Garfield first eats a mouse, than Catbert eats "cheese", and Garfield then has "cheese" in its belly - this can not be a very private belly :-)

Isn't this a nice gotcha?

The privFood variable comes from the Animal instance in the prototype of Cat that Garfield and Catbert have in common! Not even always putting a new Animal instance to the Cat prototype when creating a new Cat helps.
This is the moment when JS experts start to talk about the "prototype chain", and complex diagrams are drawn.
At some point the technical issues become so overwhelming that you forget about the nice web-page you wanted to implement; that point is trespassed here and now.

Lesson Learnt

Especially on the way to modules this is a quite frustrating experience.




ɔ⃝ Fritz Ritzberger, 2014-10-16