Implementing instance methods/variables in prototypal inheritance

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!

Answers:

Answer

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

Answer

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...
});
Answer

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

Answer

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.

Answer

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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.