Note: This works great for what it does, but there's not much of a reason to use it in modern-day projects anymore. Better tools exist now, e.g. Babel.
A small but powerful wrapper over JavaScript's prototype system that eases the creation of classes.
Supports IE6+, modern browsers, and AMD.
var FourLeggedThing = aclass({
init: function (noise) {
this.noise = noise;
},
makeNoise: function () {
alert(this.noise);
},
run: function () {
// have fun implementing this
}
});
var Cat = aclass(FourLeggedThing, {
around$init: function (supr) {
supr("meow");
},
flee: function () {
this.makeNoise();
this.run();
}
});
var precious = new Cat();
precious.flee();
precious instanceof Cat === true;
precious instanceof FourLeggedThing === true;
Returns a new class.
BaseClass
is optional. If supplied, it must have been previously created with aclass()
.
The second argument (or first, if BaseClass
is not supplied), is either an init()
function, or an object whose properties will be copied to the new class's prototype. The init
function is automatically run whenever you create a new instance. You can supply an init
in properties
, too; the former syntax is simply a convenience for when the class has no other methods.
Extends the existing classOrInstance
with values from properties
.
var Cat = aclass();
Cat.extend({
meow: function () {
alert("meow");
}
});
var angryCat = new Cat();
angryCat.extend({
// see "method modifiers" below to understand the $-syntax
after$meow: function () {
this.attack();
}
});
An API is available for augmenting methods, inspired by Moose's method modifiers.
There are two ways to use these. The most convenient way is the modifier$method
syntax, only usable within the properties
of a class definition:
var Cat = aclass(FourLeggedThing, {
// delegates to FourLeggedThing.prototype.init
around$init: function (supr) {
supr("meow");
}
});
(See below for a complete explanation of around
.)
The second way to use method modifiers is, well, as methods:
var Cat = aclass(FourLeggedThing);
// equivalent to the example above
Cat.around("init", function (supr) {
supr("meow");
});
This is necessary if a class is defined somewhere else, and you want to modify it later. It's also necessary if you want to use method modifiers on an instance of a class.
Available modifiers are listed below.
Replaces classOrInstance[methodName]
with a function than runs the original method, as well as func
. With .before()
, func
runs first. With .after()
, it runs second.
Return values from the original method and func
are ignored.
If methodName
is somewhere in the prototype chain of classOrInstance, the new method delegates calls to the parent prototype, as expected.
Replaces classOrInstance[methodName]
with function func
, which recieves the original method as its first argument. This is useful if:
- You care about the return values of any of the methods.
- You have complex code that you want to run before and after the original method.
- You may not want to run the original method at all.
around()
is the suggested way to call methodName
in the parent class. See above for an example of this.
augment()
is the inverse of around()
. The original method receives an inner
function as the first argument, which subclasses then implement via augment()
.
var Sign = aclass({
init: function (canvas, text) {
this.canvas = canvas;
this.text = text;
canvas.width = 100;
canvas.height = 100;
},
draw: function (inner) {
var context = this.canvas.getContext("2d");
inner(context);
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "#000";
context.fillText(this.text, 50, 50);
}
});
var YieldSign = aclass(Sign, {
around$init: function (supr, canvas) {
supr(canvas, "YIELD");
},
augment$draw: function (context) {
context.beginPath();
context.moveTo(0, 99);
context.lineTo(50, 0);
context.lineTo(99, 99);
context.lineTo(0, 99);
context.strokeStyle = "#000";
context.fillStyle = "#ff0";
context.stroke();
context.fill();
context.closePath();
}
});
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
YieldSign(canvas).draw();
Custom method modifiers can be created, as described below.
name
is the name of the modifier.
callback
receives three parameters: the owner of the method being modified, the name of the method being modified, and the new function (or value) that was provided. Typically, callback
should return a new function that does something with these parameters, but you can be as creative as you want.
If you're using the $-syntax, the third parameter received is the function (or value) you assign to the modifier$method
property. For example:
aclass.methodModifier("throttle", function (owner, name, func) {
// underscore's throttle() function:
// http://underscorejs.org/#throttle
return _.throttle(func, 100);
});
var A = aclass({
// There is no original updateSomething() function here, which is okay.
// We can ignore the orig argument above.
throttle$updateSomething: function () {
this.update();
}
});
Alternatively, you could implement it like this, if you’d prefer to throttle methods after-the-fact:
aclass.methodModifier("throttle", function (owner, name) {
var orig = aclass.methodOrDelegate(owner, name);
return _.throttle(orig, 100);
});
var A = aclass({
updateSomething: function () {
this.update();
}
});
A.throttle("updateSomething");
In this case, A
is the owner and updateSomething
is the name of the method to modify on A
. The aclass.methodOrDelegate
utility is described below.
If name
is an own property of owner
(i.e., owner.hasOwnProperty(name)
returns true
), then this simply returns owner[name]
. Otherwise, this returns a function that delegates to owner
’s super-class to look up name
. This allows you to “reference” a function that may not exist yet, or may exist but become modified later (possibly through a method modifier).