Making simple solutions to complex problems      
Making simple solutions to complex problems   home  
     

Javascript classical inheritance

 

Securing hierarchy and saving constructors

[2009-02-07]

Sewing example – pseudo-code

Parent works by hand and has a needle and knows how to thread a needle and sew in a straight line.

class Parent {
    private _needle = 1;
    public speed = 1;
    public threadNeedle(thread) {...}
    public sewStraight(fabric, thread) {...}
}

Child has a sewing machine and knows how to sew zigzag. But child has no needle, doesn't know how to thread a needle, nor how to sew in a straigh line.
Child is really pretty useless on it's own - except for the autonomous method blurp.

class Child
    private _zigzagThreadModifier = 0.5;
    public speed = 2;
    public sewZigzag(fabric, thread) {...}
    public blurp() {...}
}

In classical object-oriented languages (like Java) inheritance means that a new (sub) class specializes an existing (super) class. The sub class is going to inherit all properties (attributes and methods) of the super, but in order to specialize, the sub will override some of the inherited properties and/or add some new properties.

In practice, the specializing and augmenting in classical inheritance 'happens' through instantiation of super classes - i.e. calling supers' constructors.

Every time we create a Child object, the Child constructor must:

•  start out by calling Parent's constructor to initialize Parent attributes and methods (in effect creating an internal Parent)

••  procede by executing Child's own constructor, initializing Child attributes and method

•••  and in case Child has a same-named attribute or method as Parent, the Child attribute|method must override and mask the Parent attribute|method

Awareness of supers – instantiation of supers

Thus in order to implement classical inheritance, a sub class must:

1)  know which super classes it extends

2)  have a means of calling super's constructors
during object instantiation

The first thing is easy – we can just add an array to the sub class' prototype, that lists the super class(es). But since one can add/alter a prototype of class at any time (even after the class declaration, that is), we'll do it on demand (~ we don't have to do it right now).

Sewing example – Javascript

var Parent = function() {
    var _needle = 1;
    this.threadNeedle = function(thread) {  
        return thread * _needle;  
    };
    this.sewStraight = function(fabric, thread) {
        return "straight seam of length " +
                (this.threadNeedle(thread) * fabric * this.speed);
    };
};
Parent.prototype.speed = 1; // just to have something thats prototyped

var Child = function() {
    //  call super constructors(s) -----------------------------------
    if(this.__supers_Child) {  this.__supersIni_Child(this);  } 
    //  /call super constructors(s) ----------------------------------
    var _zigzagThreadModifier = 0.5;
    this.sewZigzag = function(fabric, thread) {
        if(!this.hasOwnProperty("threadNeedle")) {
            return "cant sew, doesnt know how to thread needle";
        }
        return "zigzag seam of length " + (this.threadNeedle(thread)
                * _zigzagThreadModifier * fabric * this.speed);
    };
};
Child.prototype.speed = 2; // just to have something thats prototyped

extendClass(Child, "Child", Parent); // make Child objects more capable

var child = new Child();
alert(child.sewStraight(3, 3)); // -> "straight seam of length 18"
alert(child.sewZigzag(3, 3)); // -> "zigzag seam of length 9"

The second prerequisite requires a slightly more intrusive operation – we have to modify the sub class' constructor so that it calls it's super constructor(s) before it does it's own instantiation jobs.
The sub class' constructor has to do this reasoning: "if I have a property that looks like a list of supers, I guess I also have a method for calling those supers – and I'll call that method (before I do anything else)".

The price of refraining from prototyping the Function class

The if(this.__supers_"MyClass") {
  this.__supersIni_"MyClass"(); }
is the price we have to pay for implementing the function that enables Child to extend Parent as a standalone function (here extendClass, see bottom of page) instead of a prototype method to Function.

Does it work - do we actually gain classical inheritance?

Well the object child is capable of more than just (Child) blurping – it actually does know how to sew straight and zigzag – having inherited the abilities to thread a needle and sew straight from Parent.

What's inherited:
–  all public instance and prototype properties
–  public properties of sub overrides ditto of super

Scope:
–  when calling a instance or prototype method of super, the method is executed
 in the context of super

Access, public methods:
–  all public instance methods can see all prototype properties
–  public instance methods of a sub can see public instance properties of all supers
–  methods of super can not see instance properties of sub
–  public instance methods of a class can see private properties of the same class
 (but not of other classes)
–  prototype methods can see public instance props of all classes (sub, self, super)

Access, private methods:
–  can not see any public properties, not even of same class (just like if not extended)
–  can indirectly see public instance properties (of the same class) if these are referenced
 by a private var

Instance/prototype property precedence:
–  instance props always overrides prototype props, no matter on what level the instance prop
 is set (also when instance is of super and proto is of sub, yes)

Multiple inheritance patterns supported

1)  serial inheritance – sub inherits from a super that itself inherits from another super 
–  extendClass(Intermediate, "Intermediate", Super);
 extendClass(Sub, "Sub", Intermediate);

–  the classical hierarchical inheritance pattern

2)  parallel inheritance (aka. multiple inheritance) – sub inherits from more than one super
–  extendClass(Sub, "Sub", Super_1);  extendClass(Sub, "Sub", Super_2); etc.
–  more fun than hierarchical inheritance IMHO, but requires strict name-spacing
 of the various classes' properties, otherwise things can get real messy
–  Java, PHP and C# don't support it, C++ and Python do

3)  a mixture of serial and parallel inheritance
–  go ahead, probably gets a little complicated ;-)

 

The extend function - a simplified version of ZZ_javascripts' ZZ.extendClass

If sub doesn't already extend another class: adds super constructor caller method to sub's prototype.
Always attaches super's prototype properties to sub.

/** Simplified version of ZZ.extendClass
 * @return {void} 
 * @param {class} subClass
 * @param {str} subClassName
 * @param {class} superClass */
var extendClass = function(subClass, subClassName, superClass) {
    var listOfSupers = "__supers_" + subClassName,
    callSuperConstructors = "__supersIni_" + subClassName,
    prototypeProp;
    //  only first time: enable subClass to call super constructor(s) -------------------------------
    if(!subClass.prototype[listOfSupers]) { 
        //  add list of supers to sub
        subClass.prototype[listOfSupers] = [];
        //  add super constructor caller method to sub
        subClass.prototype[callSuperConstructors] = function(subThis) {
            //  instantiate supers, and inherit all instance properties - public as well as privileged
            //  The reason this works (inheriting privileged props as well as public)
            //  is that the properties are being referenced, not copied.
            //  By refering to the props of super, super effectively exists, despite the fact that no
            //  direct reference to super itself exists in sub.
            var noOfSupers = subClass.prototype[listOfSupers].length, i, superObject, instanceProp;
            for(i = 0; i < noOfSupers; i++) {
                //  instantiate super (call super constructor)
                superObject = new subClass.prototype[listOfSupers][i](); 
                //  refer supers instance props, when sub has none of same name
                for(instanceProp in superObject) {
                    if(superObject.hasOwnProperty(instanceProp) && // its a instance of super
                    !subThis.hasOwnProperty(instanceProp)) { // and sub doesnt have such instance    
                        subThis[instanceProp] = superObject[instanceProp];         
                    }
                }
            }
            //  mask subs list of supers, and subs super ini method (this method, actually)
            subThis[listOfSupers] = null;  
            subThis[callSuperConstructors] = null;
        };
    }    
    //  /only first time ----------------------------------------------------------------------------
    //  add super to list of supers
    subClass.prototype[listOfSupers].push(superClass);
    //  attach all supers prototype properties, except those that subClass already have
    for(prototypeProp in superClass.prototype) {
        if(!(subClass.prototype[prototypeProp] !== undefined)) {             
            subClass.prototype[prototypeProp] = superClass.prototype[prototypeProp];
        }
    }
};

 


Update

[2010-05-02]

The actual implementation of classical inheritance in the ZZ javascript framework differs significantly from what's lined out here, but the basic principles are pretty much the same.