5

I've been playing around with prototypal inheritance after reading http://javascript.crockford.com/prototypal.html and having a bit of a problem with understanding how I could make use of it in the way I would use classical inheritance. Namely, all functions and variables inherited by the prototype essentially become statics unless they are overwritten by the child object. Consider this snippet:

var Depot = {   
    stockpile : [],
    loadAmmo : function (ammoType) {
        this.stockpile.push(ammoType);
    }
};

var MissileDepot = Object.create(Depot);
var GunDepot = Object.create(Depot);

stockpile and loadAmmo definitely should be in the prototype, since both MissileDepot and GunDepot have them. Then we run:

MissileDepot.loadAmmo("ICBM");
MissileDepot.loadAmmo("Photon Torpedo");

alert(MissileDepot.stockpile); // outputs "ICBM,Photon Torpedo"
alert(GunDepot.stockpile); // outputs "ICBM,Photon Torpedo"

This is expected because Neither MissileDepot nor GunDepot actually have stockpile or loadAmmo in their objects, so javascript looks up the inheritance chain to their common ancestor.

Of course I could set GunDepot's stockpile manually and as expected, the interpreter no longer needs to look up the chain

GunDepot.stockpile = ["Super Nailgun", "Boomstick"];
alert(GunDepot.stockpile); // outputs "Super Nailgun,Boomstick"

But this is not what I want. If this were classical inheritance (say Java), loadAmmo would operate on MissileDepot and GunDepot's stockpile independently, as an instance method and an instance variable. I would like my prototype to declare stuff that's common to children, not shared by them.

So perhaps I'm completely misunderstanding the design principles behind prototypal inheritance, but I'm at a loss as how to achieve what I've just described. Any tips? Thanks in advance!

4
  • you should not use uppercase for the name of objects Depot, MissileDepot, GunDepot. This goes against common conventions and hinders understanding. Commented Jan 24, 2010 at 14:49
  • Thanks Eric - I used that to denote an class, as in Ruby/Java/php/whatever. So for javascript, we use camel case for ALL objects? What's the rule of thumb Commented Jan 26, 2010 at 0:26
  • sorry for the punctuation, i meant "what's the rule of thumb?" Commented Jan 26, 2010 at 0:26
  • CapitalizedCamelCase is correct for class names, camelCase for class instances. Commented Sep 15, 2010 at 2:51

5 Answers 5

8

Javascript provides a way to do this the way U are used to :) try this:

function Depot() {   
    this.stockpile = [],
    this.loadAmmo = function (ammoType) {
        this.stockpile.push(ammoType);
    }
};

var MissileDepot = new Depot();
var GunDepot = new Depot();


MissileDepot.loadAmmo("ICBM");
MissileDepot.loadAmmo("Photon Torpedo");

alert(MissileDepot.stockpile); // outputs "ICBM,Photon Torpedo"
alert(GunDepot.stockpile); // outputs ""

And U can add the functions on the fly afterwards:

MissileDepot.blow = function(){alert('kaboom');}

Extending object with another object is also an option, but what You wanted is the fact, that OO programming in javascript is done by functions not objects with {} ;)

EDIT:

I feel bad for writing that without mentioning: The javascript "new" keyword is only for making it easier to OO veterans. Please, dig deeper into the prototypal inheritance and dynamic object creation as therein lies true magic! :)

Sign up to request clarification or add additional context in comments.

3 Comments

loadAmmo() should reside in Depot.prototype as it can be shared between instances; the way you're doing it, you create a new function object for each Depot instance
I don't think it affects the problem in this question, but it IS interesting and might be of use. Could You elaborate more? Maybe a little example to see the difference?
Depot.prototype.loadAmmo = function () { ... }; This way, there is one and only one function instance that is shared among Depot instances. If you do this.loadAmmo = function () { ... }; inside the constructor, you're assigning a new function instance to every Depot instance.
2

For the method, all works as expected. It's just the fields that you need to take care of.

What I see a lot in YUI, is that the constructor allocates the instance varialbes. 'Classes' that inherit from a parent call the constructor of their parent. Look here: http://developer.yahoo.com/yui/docs/DataSource.js.html

Example base class:

util.DataSourceBase = function(oLiveData, oConfigs) {
    ...   
    this.liveData = oLiveData;

    ... more initialization...
}

Example subclass:

util.FunctionDataSource = function(oLiveData, oConfigs) {
    this.dataType = DS.TYPE_JSFUNCTION;
    oLiveData = oLiveData || function() {};

    util.FunctionDataSource.superclass.constructor.call(this, oLiveData, oConfigs);   
};

// FunctionDataSource extends DataSourceBase
lang.extend(util.FunctionDataSource, util.DataSourceBase, {
    ...prototype of the subclass...
});

Comments

1

To achieve what you want, you need a cloning method. You don't want an inheritance prototype, you want a cloning prototype. Take a look at one of the Object.clone() functions already implemented, like prototypejs's one: http://api.prototypejs.org/language/object.html#clone-class_method

If you want to stick to some kind of prototyping, you have to implement an initialize() method that will give a stockpile property to your newly created Depots. That is the way prototypejs Classes are defined : a cloned prototype and an initialize() method : http://prototypejs.org/learn/class-inheritance

Comments

1

That's because you're trying to make a cat meow! Douglas Crockford is good, but that script you're using essentially works by looping through your parent object and copying all of its attributes into the prototype chain--which is not what you want. When you put things in the prototype chain, they're shared by all instances of that object--ideal for member functions, not ideal for data members, since you want each object instance to have its own collection of data members.

John Resig wrote a small script for simulating classical inheritance. You might want to check that out.

1 Comment

This small script looks so disturbingly familiar!
0

The secret to instance variables in JavaScript is that they are shared across methods defined in superclasses or from included modules. The language itself doesn't provide such a feature, and it may not be possible to mesh with Prototypal inheritance because each instance will need it's own instance variable capsule, but by using discipline and convention it is fairly straightforward to implement.

// Class Depot
function Depot(I) {
  // JavaScript instance variables
  I = I || {};

  // Initialize default values
  Object.reverseMerge(I, {
    stockpile: []
  });

  return {
    // Public loadAmmo method
    loadAmmo: function(ammoType) {
      I.stockpile.push(ammoType);
    },
    // Public getter for stockpile
    stockpile: function() {
      return I.stockpile;
    }
  };
}

// Create a couple of Depot instances
var missileDepot = Depot();
var gunDepot = Depot();

missileDepot.loadAmmo("ICBM");
missileDepot.loadAmmo("Photon Torpedo");

alert(missileDepot.stockpile()); // outputs "ICBM,Photon Torpedo"
alert(gunDepot.stockpile()); // outputs ""

// Class NonWeaponDepot
function NonWeaponDepot(I) {
  I = I || {};

  // Private method
  function nonWeapon(ammoType) {
    // returns true or false based on ammoType
  }

  // Make NonWeaponDepot a subclass of Depot and inherit it's methods
  // Note how we pass in `I` to have shared instance variables
  return Object.extend(Depot(I), {
    loadAmmo: function(ammoType) {
      if(nonWeapon(ammoType)) {
        // Here I.stockpile is the same reference an in the Depot superclass
        I.stockpile.push(ammoType);
      }
    }
  });
}

var nonWeaponDepot = NonWeaponDepot();
nonWeaponDepot.loadAmmo("Nuclear Bombs");

alert(nonWeaponDepot.stockpile()); // outputs ""

And that's how to do instance variables in JavaScript. Another instance variable example using the same technique.

1 Comment

Thanks Daniel - what's "Object.reverseMerge"? Couldn't find it in the browser, is it a library method?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.