Simple class declaration function to write OO code in JavaScript
This library provides a simple class declaration function. Classes defined with this function behave exactly as same as normal JavaScript constructor functions. This program aims to be as just a syntax sugar to easily declare Classes, its inheritance, Mix-ins, namespace, statics etc. without any dependencies on other libraries nor any conflicts with them.
Table of contents
in Node.js
install joo with npm npm install joo
, then require it as below.
var def = require('joo');
in HTML
load joo as a script <script type="text/javascript" src="/path/to/joo.js"></script>
, then require it as below.
var def = require_joo();
Then, define your class with def()
.
function MyClass() {}
def(MyClass).
it.provides({
doSomething: function() {}
});
Here is the whole API of this program.
var def = require_joo();
def(function() {
// This is the class constructor.
// You can call SuperClass's constructor as below.
this._super();
}).as('myapp.module.MyClass').
it.inherits(SuperClass).
it.borrows(MethodProvider, OtherProvider, AndMore).
it.hasStatic({
STATIC_PROPERTY: [],
STATIC_METHOD: function() {}
}).
it.provides({
method: function() {
// invocation of a method of the same name in SuperClass's implementation
this._super();
// below expressions refer the same static object.
this._static.STATIC_PROPERTY;
this.constructor.STATIC_PROPERTY;
myapp.module.MyClass.STATIC_PROPERTY;
}
});
The API provides .it
as a syntactic ornament which makes class declaration look like natural sentences.
Someone would claim that it causes unnecessary object reference, which influences performance.
But I think it's acceptable cost unless you declare classes thouthands of times.
Nevertheless, if you hate it, you can omit it.
def(MyClass).inherits(SuperClass).provides({...});
Now, let's take a look at each functionalities in detail.
First of all, you need to call require_joo()
, which returns a function.
To use the function, store it in any variable as you like,
such as def
, define
, declare
etc.
var def = require_joo();
// You can define constructor function in a standard way.
function BaseClass(name) {
this.name = name;
}
// Then, you can define methods by chaining '.it.provides()' method.
def(BaseClass).it.provides({
getName: function() {
return this.name;
}
});
That's it. You can use the class exactly as same as a standard JavaScript class.
var base = new BaseClass('base');
base instanceof BaseClass // true
base.constructor === BaseClass // true
base.getName(); // 'base'
You can define class inheritance as below:
function ChildClass(name, age) {
// You can call super class' constructor by 'this._super()'.
this._super(name);
this.age = age;
}
def(ChildClass).
it.inherits(BaseClass).
it.provides({
getAge: function() {
return this.age;
}
});
The most exciting thing is that the scope chain of this._super()
woks perfectly even in multiple level inheritances.
This is difficult to write in a standard way.
function GrandChildClass(name, age) {
this._super(name, age);
}
def(GrandChildClass).
it.inherits(ChildClass).
it.provides({
getName: function() {
// Even if the closest ancestor (in this case, ChildClass) doesn't implement getName() method explicitly,
// this._super() method looks up to BaseClass's getName() method.
// This is awesome, isn't it?
return 'My name is ' + this._super();
},
getAge: function() {
return 'I am ' + this._super();
},
toString: function() {
// You can override built-in 'toString()' method in the same way.
return this._super() + '(name: ' + this.name + ')'
}
});
This works perfectly as we expect.
var g = new GrandChildClass('Hiroshi', 29);
g instanceof BaseClass; // true
g instanceof ChildClass; // true
g instanceof GrandChildClass; // true
g.constructor == GrandChildClass; // true
g.getName(); // 'My name is Hiroshi'
g.getAge(); // 'I am 29'
g + '' // '[object Object](name: Hiroshi)'
Our class can borrow methods from other classes or objects through .borrows()
method.
// Mix-in provider can be an object;
var MethodProvider = {
methodA: function() {}
};
// or Classes.
function OtherProvider() {}
OtherProvider.prototype.methodB = function() {};
// Our class can borrow methods from these providers.
function OurClass() {}
def(OurClass).
it.borrows(MethodProvider, OtherProvider).
it.provides({
ownMethod: function() {}
});
Now OurClass has methodA
, methodB
, ownMethod
, but an instance of the class won't be a Mix-in provider's instance.
var c = new OurClass();
c instanceof MethodProvider; // false
c instanceof OtherProvider; // false
c.methodA();
c.methodB();
c.ownMethod();
Here is an example of static properties.
function MyClass() {}
def(MyClass).it.hasStatic({
FOO: 0,
BAR: 1,
BLAH: {
BAZ: true
}
}).provides({
method: function() {
// You can refer to static properties through ._static property in an instance method.
this._static.FOO == MyClass.FOO; // true
}
});
MyClass.FOO // 0
MyClass.BAR // 1
MyClass.BLAH.BAZ // true
It's good habitat to declare your classes in your module scope, then export it to global scope under your namespace;
To achieve this, use .as()
method.
(function() {
var def = require_joo();
function ModuleLocalClass() {}
def(ModuleLocalClass).as('myapp.module.foo.bar.Blah').
it.provides({/*...*/});
})();
new ModuleLocalClass() // reference error.
var blah = new myapp.module.foo.bar.Blah();
blah.constructor // ModuleLocalClass
If you pass a second argument as a context object to .as()
method, classes are exported under the context instead of a global scope.
(function() {
var def = require_joo();
var context = {};
def(function() {}).as('myapp.module.Class', context);
new context.myapp.module.Class();
})();
Some parts of the code, especially .this._super()
method implementation was inspired by John Resig's Simple JavaScript Inheritance, but I further improved several points.
In this library,
-
init
method are not mandatory.
John Resig's implementation and many other libraries which provides original class system often requires us to declare init
method as a constructor function. It is indeed convensional, but not standard. I wanted a simple syntax to use normal function as a class constructor. This is the main reason I wrote this code.
- you can easily implement Mix-in class with
.borrow()
method. -
this._super()
method works appropriately both in constructors and in methods, even under multiple level inheritance. -
.constructor
property correctly points to its constructor. - declaration sentences look like a natural language, and has explicit semantics.
I hope this simple library would be useful to other developers.
define(MyClass).as('myapp.module.MyClass').
it.inherits(SuperClass).
it.borrows(Provider, OtherProvider).
it.provides({
ownMethod: function() {
}
});