Max Koretskyi
Max Koretskyi

Reputation: 105459

Understanding prototype inheritance

I've drawn the following picture demonstrating how the objects are inherited ( function constructors are marked as blue, objects created from those constructors are marked as green):

enter image description here

Here is the code creating such hierarchy:

function Figure() {}

function Rect() {}
Rect.prototype = new Figure();

function Square() {}
Square.prototype = new Rect();

function Ellipse() {}
Ellipse.prototype = new Figure();

function Circle() {}
Circle.prototype = new Ellipse();

Now I want to check if new Square() is inherited from the Rect, so here is how I expect JavaScript engine to check it:

var s = new Square();
s instanceof Rect // ?

s.__proto__ === Rect.prototype // false
new Rect()      new Figure()

s.__proto__.__proto__ === Rect.prototype // true
new Figure()              new Figure()

So s instanceof Rect should return true. This is expected and is actually what is returned if I run the code. But then I want to check if new Circle() is inherited from the Rect, so I follow the same logic:

var c = new Circle();
c instanceof Rect // ?

c.__proto__ === Rect.prototype // false
new Ellipse()      new Figure()

c.__proto__.__proto__ === Rect.prototype // true
new Figure()              new Figure()

So, using this checking logic c instanceof Rect should return true, but if I actually run the code, c instanceof Rect returns false. Am I misunderstanding the mechanism of the instanceof operator?

Upvotes: 10

Views: 208

Answers (3)

Paul
Paul

Reputation: 141839

The fact that Circles and Squares both have a Figure in their prototype chains means that c instanceof Figure and s instanceof Figure are both true, but they each have their own Figure, because new Figure() was called twice. Note that new Figure() != new Figure(), since objects in Javascript are never equal to other objects. Here is an update to your diagram showing what is actually going on in your code above. You can see that Circles and Squares each have a distinct Figure in their prototype chains:

enter image description here

Notice that you could change your code slightly, so that you reuse one figure as the prototype for both Rect and Ellipse, which would equate to the diagram in your original post, and would result in exactly the behaviour you expected to see:

function Figure() {}
var f = new Figure();

function Rect() {}
Rect.prototype = f;

function Square() {}
Square.prototype = new Rect();

function Ellipse() {}
Ellipse.prototype = f;

function Circle() {}
Circle.prototype = new Ellipse();

console.log( (new Circle) instanceof Rect ); // true

Upvotes: 3

Tamas Hegedus
Tamas Hegedus

Reputation: 29906

Your logic is right, but the initial assumptions were a bit wrong. It is possible to emulate regular class based inheritance with prototypes.

To reproduce the structure you have drawn us, I created the following code:

function Figure() {}
function Rect() {}
function Square() {}

function Ellipse() {}
function Circle() {}

Ellipse.prototype = Rect.prototype = new Figure();

Square.prototype = new Rect();
Circle.prototype = new Ellipse();

console.log("is Figure: " + (new Circle() instanceof Figure));
console.log("is Ellipse: " + (new Circle() instanceof Ellipse));
console.log("is Rect: " + (new Circle() instanceof Rect));

As you can see, new Circle() instanceof Rect returns true as you provisioned. The problem is that by setting Ellipse.prototype and Rect.prototype to the same object, they basically become the same type (with multiple constructors).

So how do you fix it? Create different Figure instances for prototypes, like this:

function Figure() {}
function Rect() {}
function Square() {}

function Ellipse() {}
function Circle() {}

Ellipse.prototype = new Figure();
Rect.prototype = new Figure();

Square.prototype = new Rect();
Circle.prototype = new Ellipse();

console.log("is Figure: " + (new Circle() instanceof Figure));
console.log("is Ellipse: " + (new Circle() instanceof Ellipse));
console.log("is Rect: " + (new Circle() instanceof Rect));

And now the result is what everyone would expect.

EDIT

I've redrawn your picture and drawn another one which illustrates how the objects really are based on your textual example, which is the same as my second code.

The original one: I highlighted the references which are taken in the expression Rect.prototype === new Circle().__proto__.__proto__:

enter image description here

The second one:

enter image description here

PS

Today in 2016, not Circle.prototype = new Ellipse() is the way you should implement inheritance, but use the standard class inheritance instead:

class Figure {}
class Rect extends Figure {}
class Square extends Rect {}
class Ellipse extends Figure {}
class Circle extends Ellipse {}

console.log("new Circle is Figure: " + (new Circle() instanceof Figure));
console.log("new Circle is Rect: " + (new Circle() instanceof Rect));

Upvotes: 8

zzzzBov
zzzzBov

Reputation: 179046

Given the example code in your question, c.__proto__.__proto__ === Rect.prototype returns false. new Figure is called twice, and creates two different Figure instances.

Different object instances are not equal.

function Figure() {}

function Rect() {}
Rect.prototype = new Figure();

function Square() {}
Square.prototype = new Rect();

function Ellipse() {}
Ellipse.prototype = new Figure();

function Circle() {}
Circle.prototype = new Ellipse();

var c = new Circle();

console.log('c instanceof Rect:', c instanceof Rect);

console.log('c.__proto__ === Rect.prototype', c.__proto__ === Rect.prototype);

console.log('c.__proto__.__proto__ === Rect.prototype', c.__proto__.__proto__ === Rect.prototype);

Upvotes: 4

Related Questions