I needed JavaScript inheritance that offered calling of overridden methods (super calls).
Furthermore I needed a possibiliy to add behaviour to instances (mixins).
Mixins should not only be full classes including mentioned inheritance but also be able to override its instance' method.
Below is what I came up with. It does what I needed it to do. May I ask to have a look at the implementation and its possible shortcomings?
log = function( s ) {
var dn = document.getElementById( "output" );
dn.innerHTML = dn.innerHTML + "</br>" + s;
}
function Class() { }
Class.prototype.construct = function() {};
Class.create = function() {
// create new instance
var oClass = arguments[0];
var args = arguments[1];
var oInstance = new oClass( args );
// in case of mixins add behaviour to instance
if( args && args.mixins ) {
var c = args.mixins.length;
// loop through mixins prototypes an attach
for( var ix=0; ix<c; ix++ ) {
var oMixin = args.mixins[ ix ].o.prototype;
// set flag whether mixin has init function
var bInit = false;
for (var sMember in oMixin) {
if( sMember == "_init" ) {
// mixin has init fuction
bInit = true;
}
else {
var oMember = oMixin[sMember];
if (oMember instanceof Function &&
oInstance[sMember] ) {
// in case of overrides save mixee's orig
// method and add _mixee() to call it
if( ! oInstance.lfnMixee ) {
oInstance.lfnMixee = {};
oInstance._mixee = function() {
var that = arguments[0];
var args = arguments[1];
var sfn = arguments[2];
return that.lfnMixee[ sfn ].apply( that, args );
};
}
var fn = oInstance[ sMember ];
oInstance.lfnMixee[sMember] = fn;
}
// augment mixin's members
oInstance[ sMember ] = oMember;
}
}
if( bInit ) {
// call mixin's init function with passed on arguments
var aConfig = args.mixins[ ix ].aConfig;
oMixin._init.apply( oInstance, [ aConfig ] );
}
}
}
if( oInstance._init ) {
// call init function if exists
oInstance._init( args );
}
return oInstance;
};
Class.extend = function(o) {
var oNew = function() {
if (arguments[ 0 ] !== Class) {
// call constructor if not base
this.construct.apply( this, arguments );
}
};
// get prototype and hook to parent
var oPrototype = new this(Class);
var oParent = this.prototype;
for (var sMember in o) {
var oMember = o[sMember];
if (oMember instanceof Function) {
oMember.oParent = oParent;
// in case we override add method
// _parent() to call parent's function
oPrototype._parent = function() {
var that = arguments[0];
var args = arguments[1];
var sfn = arguments[2];
return args.callee.oParent[ sfn ].apply( that, args )
};
}
// augment with members
oPrototype[sMember] = oMember;
}
// set prototype
oNew.prototype = oPrototype;
// and make it extendable
oNew.extend = this.extend;
return oNew;
};
/* ****** TEST CODE ********* */
var Person = Class.extend({
name : null,
_init : function( a ) {
this.name = a.name;
},
say : function( s ) {
s = '('+this.name+') "' + s +'"';
log( s );
}
});
var Moderator = Person.extend({
_init : function( a ) {
this._parent( this, arguments, '_init' );
this.name = "[mod|" + this.name +"]";
},
say : function( s ) {
log( 'as moderator I say: ' );
this._parent( this, arguments, 'say' );
},
read : function( s ) {
s = "..."+s;
log( s );
}
});
var Megaphone = Class.extend({
say : function( s ) {
log( "<YELL>" );
this._mixee( this, arguments, 'say' );
log( "</YELL>" );
}
});
var Googles = Class.extend({
type : "Googles",
color : "transparent",
read : function() {
log( "<SHARP>" );
this._mixee( this, arguments, 'read' );
log( "</SHARP>" );
},
clean : function(){
log( this.name + " cleaning "+ this.color +" " + this.type );
}
});
var Shades = Googles.extend({
type : "Shades",
_init : function( a ) {
this.color = a.color;
},
read : function() {
log( "<"+this.color+">" );
this._parent( this, arguments, 'read' );
log( "</"+this.color+">" );
},
clean : function(){
this._parent( this, arguments, 'clean' );
}
});
var john = Class.create( Person, {
name : "john",
mixins: [ {
o : Googles
} ]
} );
var joseph = Class.create( Moderator, {
name : "joseph",
mixins: [ {
o : Shades,
aConfig : { color : "blue" }
}
, { o : Megaphone }
]
} );
john.say( 'hello' );
john.clean();
log( '---------' );
joseph.say( 'hello' );
joseph.read( 'agenda of meeting' );
joseph.clean();
<div id="output"></div>