Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why wouldn't I use Child.prototype = Parent.Prototype rather than Child.prototype = new Parent(); for Javascript inheritance?

I don't understand this behavior in javascript for inheritance I've always seen it defined like so :

function GameObject(oImg, x, y) {

    this.x = x;
    this.y = y;
    this.img = oImg;

    this.hit = new Object();
    this.hitBox.x = x;
    this.hitBox.y = y;
    this.hitBox.width = oImg.width;
    this.hitBox.height = oImg.height;

}

Spaceship.prototype = new GameObject();
Spaceship.prototype.constructor = Spaceship;

function Spaceship(){
    console.log("instantiate ship");
    GameObject.apply(this, arguments);
    this.vx = 0;
    this.vy = 0;
    this.speed = 3;
    this.friction = 0.94;
}

But in my case, these lines :

    this.hitBox.width = oImg.width;
    this.hitBox.height = oImg.height;

When I do a console.log(this) in my Spaceship constructor, I can see that the proto property is set to Spaceship instead of GameObject, if I remove them, it is set to GameObject.

And if I use :

 Spaceship.prototype = GameObject.prototype;

I have no more problems with that. The reason that this blocks me is that I have another object with an add() method and it checks that the object inerhits of GameObject with this code :

 if(object instanceof GameObject)

I don't understand what those two lines can probably change so that inheritance is broken when they are present and I'm not sure doing inheritance the second way is good. Could someone enlighten me about this please ? :)

like image 716
Geoffrey H Avatar asked Jun 18 '12 17:06

Geoffrey H


2 Answers

If you do

Spaceship.prototype = GameObject.prototype;

Then they both refer to the same object, so you might as well have everything in GameObject, if you add something to Spaceship.prototype, it will be added to GameObject.prototype as well. You can easily test it by adding something to Spaceship.prototype after the assignment. For example, in your case you can see that GameObject.prototype.constructor is actually Spaceship.

As for

Spaceship.prototype = new GameObject();

This invokes the constructor which might have undesired side effects, you rather want to use:

Spaceship.prototype = Object.create(GameObject.prototype);

Where the used Object.create functionality here comes down to:

Object.create = function( proto ) {
    function f(){}
    f.prototype = proto;
    return new f;
};

Modern browsers already have the function though.

like image 55
Esailija Avatar answered Sep 23 '22 04:09

Esailija


It was never properly explained why you were getting weird behavior with this.hitBox (I think that's what you were trying to say).

If you do inheritance by invoking the parent's constructor to create a prototype, that parent's constructor is executed once to create an instance of the parent type, and then all instances of the child type will share that one instance as their prototype.

The problem with this is that if that constructor has any lines that assign mutable objects to this, then those objects will be properties on that prototype and any modifications to those objects will be reflected across all instances of the child type:

Spaceship.prototype = new GameObject();
Spaceship.prototype.constructor = Spaceship;

var sps1 = new Spaceship();
var sps2 = new Spaceship();

sps1.hitBox.x = 9;
sps2.hitBox.x = 12;
console.log(sps1.hitBox.x);  // 12   (oh noes! what happened)
console.log(sps2.hitBox.x);  // 12

(there are other, similar problems with the "call a constructor to make a prototype" approach, but I'll just leave it here on that point)

@Esailija's suggestion to use Object.create(baseObject) is the first step to solving this problem. It creates a new object whose prototype is baseObject, but without the stuff that is set up in the constructor (This is a good thing, but it needs to be accounted for. Read on...).

As I just said, this will create an object where the initialization logic in the parent's constructor has never run, but in most cases that logic is relevant to the object's functionality. So there is one more thing you need to do, which is to have the child constructor call the parent constructor:

function Spaceship(oImg, x, y) {
    // call parent constructor on this object and pass in arguments.
    // you could also use default values for the arguments when applicable
    GameObject.call(this, oImg, x, y);

    // remainder of Spaceship constructor...
}

This will ensure that the parent constructor logic runs separately for every new Spaceship, and carries out the necessary initialization tasks.

like image 20
JLRishe Avatar answered Sep 23 '22 04:09

JLRishe