Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS Prototypal Inheritance: childs use the same parent properties?

Let's say I have Player object:

var player = function(name) {
    this.handlers = {};
}

player.prototype.on = function(event, callback) {
    if (!this.handlers[event]) {
        this.handlers[event] = [];
    }
    this.handlers[event].push(callback);
}

It works great, I can create players and each will have its own set of handlers. Now suppose I need to inherit from player:

var testPlayer = function(name) {
    this.name = name;
};

testPlayer.prototype = new player();

Now when I create testPlayer's, each of them share the same handlers property:

var adam = new testPlayer('Adam');
adam.on('test', function(){});

var eve = new testPlayer('Eve');
// eve.handlers == {'test':<function>}

What am I missing here? I understand than every testPlayer's prototype is the same new player object I create when describing child class. But is there some way for all testPlayers to have their own set of handlers?

like image 257
Kuroki Kaze Avatar asked Apr 03 '13 17:04

Kuroki Kaze


2 Answers

The problem here is that handlers is a property that was added in the constructor, so when you do

testPlayer.prototype = new player();

you're adding every property of a brand new player object to testPlayer.prototype, and that includes handlers.

So, there's a handlers property in every testPlayer object, and when you add a property to handlers you're adding the property to the object in the prototype, and not of the testPlayer object.

In short, when calling the on method you're adding a property to testPlayer.prototype.handlers, not adam.handlers or eve.handlers.

To be safe, define:

var testPlayer = function(name) {
    this.name = name;
    this.handlers = {};
};
like image 25
MaxArt Avatar answered Oct 04 '22 15:10

MaxArt


That sure looks strange for those used to classical inheritance, but it's how prototypal inheritance works. To have a separate handlers object per instance, you need to specify one on the child constructor. That will shadow the prototype's property with the same name:

var testPlayer = function(name) {
    this.name = name;
    this.handlers = {};
};

testPlayer.prototype = new player();

Another solution would be to create this shadowing property on-demand, from your on method:

player.prototype.on = function(event, callback) {
    // Check for a handlers property on the instance
    if(!this.hasOwnProperty('handlers') {
        this.handlers = {};
    }
    if (!this.handlers[event]) {
        this.handlers[event] = [];
    }
    this.handlers[event].push(callback);
}

Interesting fact

This is only a problem if you're modifying properties of an object (or array) on the prototype. If you try to assign to properties that live on the prototype, a local shadowing property will be created automatically (you can't assign to prototype properties from instances).

like image 132
bfavaretto Avatar answered Oct 04 '22 13:10

bfavaretto