Skip to content

Instantly share code, notes, and snippets.

@Skateside
Last active December 14, 2015 15:18
Show Gist options
  • Save Skateside/39e526240f1065203a04 to your computer and use it in GitHub Desktop.
Save Skateside/39e526240f1065203a04 to your computer and use it in GitHub Desktop.
A handy function that creates a class in JavaScript
var createClass = (function () {
'use strict';
// Basic no-operation function
var noop = function () {
return;
},
// Tests to see whether or not regular expressions can be called on
// Functions.
fnTest = (/return/).test(noop)
? (/[\.'"]\$super\b/)
: (/.*/);
// Basic function for looping over objects.
function forIn(obj, handler, context) {
Object.keys(obj).forEach(function (key) {
handler.call(context, key, obj[key]);
});
}
// Basic function for extending one object with keys of another.
function augment(source, additional) {
forIn(additional, function (name, method) {
source[name] = method;
});
return source;
}
/**
* Class.addMethod(name, method)
* - name (String): Name of the method to add.
* - method (Function): Function to add.
*
* Adds a method to the current class's `prototype`. As well as simple
* convenience, this method also handles the `$super` magic property.
*
* var Foo = createClass({
* init: function (bar) {
* this.bar = bar;
* }
* });
*
* var inst = new Foo(1);
*
* Foo.addMethod('getBar', function () {
* return this.bar;
* });
*
* inst.getBar(); // -> 1
*
**/
function addMethod(name, method) {
var parent = this.parent;
this.prototype[name] = (typeof method === 'function' &&
typeof parent[name] === 'function' &&
fnTest.test(method))
? function () {
var hasSuper = '$super' in this,
temp = this.$super,
ret = null;
this.$super = parent[name];
ret = method.apply(this, arguments);
if (hasSuper) {
this.$super = temp;
} else {
delete this.$super;
}
return ret;
}
: method;
}
/** related to: Class.addMethod
* Class.addMethods(proto)
* - proto (Object): Key/Value pairs of names/methods to add.
*
* Helper function for adding multiple methods to a class's `prototype`.
**/
function addMethods(proto) {
forIn(proto, this.addMethod, this);
}
/**
* Class.extend(name[, method])
* - name (String|Object): Name of the method to add, or all methods to add.
* - method (Function): Function to add, if `name` is a `String`.
*
* Helper function either adding a single method to a class or multiple.
* Although using [[Class.addMethod]] or [[Class.addMethods]] may be
* slightly faster, this method can be more convenient.
**/
function extendClass(name, method) {
if (name && typeof name === 'object') {
addMethods.call(this, name);
} else {
addMethod.call(this, name, method);
}
}
/**
* createClass([Base, ]proto) -> Class
* - Base (Function): Constructor to use as a base.
* - proto (Object): Prototype for the new class.
*
* Creates a new class. The `init` method is used as the constructor if it
* exists.
*
* var Foo = createClass({
* init: function (bar) {
* this.bar = bar;
* }
* });
*
* var inst = new Foo(1);
* inst.bar; // -> 1
*
* Because `init` is essential for a class's operation, a basic
* "no-operation" will be added (see [[$f.noop]]).
*
* var Fii = createClass({
* add5: function (n) {
* return n + 5;
* }
* });
*
* var flop = new Fii();
* flop.add5(5); // -> 10
*
* To inherit from another class, simply provide a `Base`.
*
* var Bar = createClass(Foo, {
* getBar: function () {
* return this.bar;
* }
* });
*
* var inst2 = new Bar(2);
* inst2.getBar(); // -> 2
*
* When inheriting, all methods are granted access to a magical `$super`
* property. This property will call the parent's method and allow
* arguments to be passed through.
*
* var Baz = createClass(Bar, {
*
* init: function (bar, baz) {
*
* this.$super(bar);
* this.baz = baz;
*
* }
*
* });
*
* var inst3 = new Baz(3, 4);
* inst3.getBar(); // -> 3
* inst3.baz; // -> 4
*
**/
return function (Base, proto) {
// Base function for the new class. All new classes push everything into
// an init method.
function Class() {
return this.init.apply(this, arguments);
}
// Allow the Base to be optional.
if (!proto) {
proto = Base;
Base = Object;
}
// Expose a prototype extension method that enables the $super magic
// method.
augment(Class, {
addMethod: addMethod,
addMethods: addMethods,
extend: extendClass,
parent: Base.prototype
});
// Inherit from Base.
Class.prototype = Object.create(Base.prototype);
// Add all methods to the new prototype.
addMethods.call(Class, proto);
// Basic constructor hack.
Class.prototype.constructor = Class;
// Allow a class to me made without a constructor function.
if (typeof Class.prototype.init !== 'function') {
Class.prototype.init = noop;
}
// Return the constructor.
return Class;
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment