joo

1.0.5 • Public • Published

JOO

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() {
    }
});

Readme

Keywords

none

Package Sidebar

Install

npm i joo

Weekly Downloads

2

Version

1.0.5

License

none

Last publish

Collaborators

  • hiroshi.kuwabara