10

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 ? :)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Geoffrey H
  • 1,280
  • 2
  • 10
  • 33
  • Do note that `instanceof` is bad practice, if you can avoid using it, you probably should. – Jasper Jun 18 '12 at 18:02
  • @Bergi It isn't proper use of polymorphism and might cause problems when more classes are added to your inheritance tree. In other words, `instanceof` generally doesn't mix very well with the Open/Closed principle. – Jasper Jun 18 '12 at 18:56
  • 2
    @Jasper: That sounds like problems with the classes. If you do inheritance right, there is no reason not to use "instanceof" where appropriate. – Bergi Jun 18 '12 at 19:01
  • @Bergi What do you use instead ? I suppose there is no problem with adding one more inheritance (for example, an object that would inherit Spaceship and still have my `(object instanceof GameObject)` evaluate to true, right ? I mean in the mozila course they take the example of some object and do this `(someobject.prototype instanceof Object)` and it's working since all objects inherit from `Object` – Geoffrey H Jun 18 '12 at 23:13
  • @GeoffreyHug: I use it, ask @Jasper what he recommends. Btw: `Object.create(null) instanceof Object === false` – Bergi Jun 19 '12 at 07:29
  • @Bergi: The problem is not in the classes but the way you are (ab)using polymorphism when using `instanceof`. Try reading up on the Open/Closed principle, it is one of the most important principles for "proper" OOP design. Also, I said to use something else *if possible*. This means that it usually isn't appropriate, but when it is you should indeed be using it. – Jasper Jun 20 '12 at 14:21
  • @GeoffreyHug that isn't a question that can be asked without a context, as it differs from ontext to context. However, if you give me something that you want to do with `instanceof` I can see if I can give you an alternative that is "neater". – Jasper Jun 20 '12 at 14:23
  • @Jasper: Could you explain how the Open/Closed principle is relevant for the OP's use of instanceof? – Bergi Jun 20 '12 at 15:02
  • @Bergi I don't know what the OP's use of `instanceof` is. I know nothing other than that he uses it inside an `if` statement. That's why my original comment about `instanceof` was general rather than specific. – Jasper Jun 20 '12 at 15:07
  • Possible duplicate of [Benefits of using `Object.create` for inheritance](http://stackoverflow.com/q/17392857/1048572) – Bergi Nov 10 '14 at 17:31
  • 1
    @Bergi I know this is an old thread (but you just closed another question as a duplicate of this one). In an attempt to clear up the confusion in this comment section, it seems that sloppy use of `instanceof` can violate the Open/Closed principle (see [page 23 here](http://kti.tugraz.at/staff/rkern/courses/sa/2012/slides_oo.pdf)), but I would say that using it prudently to check for instances of a specific base class does not do so. The concerns with `instanceof` are a bit more relevant in strongly typed languages, where it can tend to undermine the type system. – JLRishe Jan 17 '15 at 22:45
  • @JLRishe: Thanks, that's a good read. Indeed, the OPs example looks much like those presented in the slides, but we can't be sure. – Bergi Jan 17 '15 at 22:59

3 Answers3

14

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.

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • Thank you very much this solved my problem and I understand inheritance better now ! :) – Geoffrey H Jun 18 '12 at 18:16
  • @GeoffreyHug did you notice you don't define `.hitBox` anywhere? `this.hit = new Object(); this.hitBox.x = x;` should probably be `this.hitBox = new Object();` – Esailija Jun 18 '12 at 18:18
  • Yes, I saw it just after posting the comment, I had made so many changes ^^ – Geoffrey H Jun 18 '12 at 18:19
  • 1
    You should mention that when using `Object.create()` for this, you'll usually need to call the parent constructor from the child constructor. – JLRishe Jan 17 '15 at 20:53
2

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.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
0
function GameObject(oImg, x, y) {

    this.x = x;
    this.y = y;
    this.img = oImg || {width:null, height: null};

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

}


function Spaceship(){
    GameObject.apply(this, arguments);
    this.vx = 0;
    this.vy = 0;
    this.speed = 3;
    this.friction = 0.94;
}
Spaceship.prototype = new GameObject();

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

sps1.hitBox.x = 9;
sps2.hitBox.x = 12;
console.log(sps1.hitBox.x);  // 9
console.log(sps2.hitBox.x);  // 12
vinayakj
  • 5,591
  • 3
  • 28
  • 48