Skip to content

Instantly share code, notes, and snippets.

@Maksims
Last active January 19, 2017 18:18
Show Gist options
  • Save Maksims/8070893 to your computer and use it in GitHub Desktop.
Save Maksims/8070893 to your computer and use it in GitHub Desktop.
Small implementation of classes with: extend, implement, and check methods
// base class
function Class() { }
// base class name
Class.prototype.className = 'Class';
// lists all parent classes and outputs JSON with it
Class.prototype.toString = function() {
var str = '';
var next = this.__proto__;
while(next && next.className) {
str = next.className + ':' + str;
next = next.__proto__;
}
return str.slice(0, -1) + ' ' + JSON.stringify(this);
};
// check if class implements specific interface
Class.prototype.implements = function(item) {
var next = this;
while(next) {
if (next.classImplementations) {
for(var i = 0, len = next.classImplementations.length; i < len; i++) {
if (next.classImplementations[i] == item) {
return true;
}
}
}
next = next.__proto__;
}
return false;
};
// check if class extends another class
Class.prototype.extends = function(item) {
return this instanceof item;
};
// check typeof class
Class.prototype.typeof = function(item) {
return item == Class;
};
// implement class extension
Class.extend = function(options) {
// name is mandatory
if (! options['name']) throw new Error('name option should be provided for a Class');
// check for reserved keys
var reserved = [
'super',
'typeof', 'extends', 'implements',
'className', 'classImplementations', 'classConstructor'
];
reserved.forEach(function(name) {
if (options[name]) throw new Error('could not extend class ' + options['name'] + ' - ' + name + ' is reserved key');
});
// keep in upper scope variables
var _super = this.prototype;
var className = options['name'];
var classImplementations = options['implement'];
var classConstructor = options['constructor'];
// class
function proto(args) {
args = args || { };
var i, len;
// if first, call own constructor as well
var first = ! args.__secondary;
args.__secondary = true;
// call parent constructor
_super.constructor.call(this, args);
// for each implementation of parent
if (_super.classImplementations) {
for(i = 0, len = _super.classImplementations.length; i < len; i++) {
// call its constructor
_super.classImplementations[i].call(this);
}
}
// if parent has consstructor, call it
if (_super.classConstructor) {
_super.classConstructor.call(this, args);
}
// if first
if (first) {
// check and call implementations
if (classImplementations) {
for(i = 0, len = classImplementations.length; i < len; i++) {
classImplementations[i].call(this, args);
}
}
// call own constructor
classConstructor.call(this, args);
}
}
// inherit prototype
proto.prototype = Object.create(this.prototype);
// implement
if (options['implement']) {
options['implement'].forEach(function(item) {
for(var name in item.prototype) {
proto.prototype[name] = item.prototype[name];
}
});
}
// set constructor
proto.prototype.constructor = proto;
// a way to call parents method
proto.prototype.super = function(method, args) {
if (! (args instanceof Array)) {
args = [ args ];
}
return _super[method].apply(this, args);
};
// check for typeof
proto.prototype.typeof = function(item) {
return item == proto;
};
// rename options
options['className'] = options['name'];
options['classConstructor'] = options['constructor'].name != 'Object' ? options['constructor'] : function() { };
options['classImplementations'] = options['implement'];
// remove garbage
delete options['name'];
delete options['constructor'];
delete options['implement'];
// implement own extensions
for(var name in options) {
proto.prototype[name] = options[name];
}
// allow class to be extended
proto.extend = Class.extend;
// return class
return proto;
};
@Maksims
Copy link
Author

Maksims commented Dec 21, 2013

Usage example, shows how to extend classes as well as implement classic prototypes:

// just classic prototype, can be with inheritance
function Charlie() {
    this.delta = 'myDelta';
}
// some method for classic prototype
Charlie.prototype.work = function() {
    this.delta += Math.random();
};

// parent class
var Parent = Class.extend({
    name: 'Parent',
    constructor: function(options) {
        this.foo = options.foo || 'no foo!';
    },
    fooMethod: function() {
        return this.foo.toUpperCase();
    },
    bazMethod: function() {
        return this.foo + ' extra text';
    }
});

// child class that extends from Parent and implements Charlie
var Child = Parent.extend({
    name: 'Child',
    implement: [ Charlie ],
    constructor: function(options) {
        this.bar = options.bar || 'no bar!';
    },
    bazMethod: function(ext) {
        // this method will call parents method
        return this.super('bazMethod') + ext;
    }
});

// create exemplar of Child
var child = new Child({
    foo: 'imFoo'
});

child.fooMethod(); // 'IMFOO'
child.bazMethod(' hey!'); // 'imFoo extra text hey!'
child.delta; // 'myDelta'
child.work(); // will append random number to 'delta'
child.delta; // 'myDelta0.6905072722584009'

child.implements(Charlie); // true
child.extends(Parent); // true
child.typeof(Child); // true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment