tau
tau

Reputation: 6749

JavaScript prototypal inheritance workaround

Here is an example:

var Box = function() {
    this.id = generateUniqueId();
};
Box.prototype = {
    add:function(parent) {
        parent.appendChild(this.elm);
    }
};

var NewBox = function() {
    this.newThing = true;
};
NewBox.prototype = new Box();
NewBox.prototype.remove = function() {
    this.elm.parentNode.removeChild(this.elm);
};

var a = new NewBox();
var b = new NewBox();
alert(a.id); // alerts 0
alert(b.id); // also alerts 0! :@

I would like to have a and b (basically each time I create a NewBox) have their own id. I understand that NewBox is using prototypal inheritance and is thus inheriting from a single instance of the generated id that it gets from Box, but I would like it so that each NewBox gets its own id without having to explicitly say that inside the NewBox constructor.

Maybe it's impossible or I'm doing something really wrong, so please help!

Thanks a lot!

Upvotes: 0

Views: 176

Answers (3)

Christian C. Salvadó
Christian C. Salvadó

Reputation: 827236

In your example, the Box constructor gets executed only when you set the NewBox.prototype object.

You could workaround this by calling the Box constructor function inside NewBox with the Function.prototype.apply method, to set the this value and forward all the argument values, for example:

//..
var NewBox = function() {
    Box.apply(this, arguments);
    this.newThing = true;
};
//..

var a = new NewBox();
var b = new NewBox();

// assuming your `generateUniqueId` function
// increments a number

alert(a.id); // will alert 1
alert(b.id); // will alert 2

Now, each time the NewBox constructor is called to create a new object instance (new NewBox();), it will call the Box function to apply all its logic on it. This will help you to avoid repeating the logic of the parent constructor over and over.

The apply, is used call the "parent" constructor function setting the this value to the object instance that is being created by the NewBox constructor and we pass all arguments provided to this function.

Your example also shows a common problem, when you express inheritance relationship through NewBox.prototype = new Box(); the Box constructor gets called and it has side effects, this new object will be initialized and your generateUniqueId function will be executed for the first time, if you want to avoid that, you need to either use a temp constructor, just to make a new object that inherits from Box.prototype, or use the new ECMAScript 5 Object.create method for the same purpose, for example:

function inherit(o) {
  function Tmp() {}
  Tmp.prototype = o;
  return new Tmp();
}

//.....
NewBox.prototype = inherit(Box.prototype);

Or:

NewBox.prototype = Object.create(Box.prototype);

In that way, you express the same inheritance hierarchy without running your Box constructor that first time, avoiding any side effect that it might cause.

At last but not least, whenever you replace a function's prototype property is always recommended to restore the constructor property of this new prototype object, otherwise it will point to the wrong function.

In your example, since you replace the NewBox.prototype with a new instance of Box, the NewBox.prototype.constructor property will point to Box, (your instances are affected, e.g. a.constructor === Box; // true) instead of to NewBox as you would expect, we need to set it back, e.g.:

NewBox.prototype = someObject; // as any of the examples above
NewBox.prototype.constructor = NewBox;

You could abstract those details into a function, as I did in the inherit function above:

function inherits(child, parent) {
  var obj, Tmp = function () {};
  Tmp.prototype = parent.prototype;
  obj = new Tmp();
  child.prototype = obj;
  child.prototype.constructor = child;
}

//...

// instead of setting the `NewBox.prototype` manually
inherits(NewBox, Box); // "NewBox inherits from Box"

//...

Upvotes: 6

James Khoury
James Khoury

Reputation: 22319

Matt Ball has the right idea. Instead try:

var Box = (function(){ 
    var numberOfBoxes = 0;
    function() {
        this.id = numberOfBoxes++;
    }
})();

Or in the case you want all your (different) classes to have unique ids:

var generateUniqueID = (function(){
    var runningCount = 0;
    return function (){
        return runningCount++;
    }
})();

var Box = function() {
    this.id = generateUniqueId();
};

var NewBox = function() {
    this.id = generateUniqueId();
    this.newThing = true;
};

Upvotes: 1

Matt Ball
Matt Ball

Reputation: 359776

Maybe it's impossible or I'm doing something really wrong, so please help!

You're doing something wrong.

If you want instance-specific values, initialize them in the constructor, not the prototype.

Upvotes: 2

Related Questions