Overview
oolib.js is a tiny JavaScript library that provides an original solution to the age-old problem of JavaScript OOP encapsulation. It offers the following features:
- Real encapsulation of object fields and private methods.
- Class inheritance. (The fields and private methods are accessible by the subclasses.)
- Support of object initialization and deinitialization.
- Super method call.
- Intuitive syntax.
- Only 1.9 kilobytes minified / 0.9 kilobytes gzipped.
Getting started
Download the library from here: https://github.com/idya/oolib
Include oolib.js into your HTML file with something like this:
<script src='path/to/oolib.js'></script>
This will create a global namespace object oo
.
The library is also provided as an A(synchronous) M(odule) D(efinition) module (RequireJS, curl.js, etc.), and as a CommonJS module (typically for server-side environments).
The basics
We can define a class (ie a constructor function) with the following syntax:
var MyClass = oo.createClass({
_create: function(foo) {
this.myField = foo;
},
_myPrivateMethod: function(bar) {
return this.myField + bar;
},
myPublicMethod: function(baz) {
return this._myPrivateMethod(baz);
}
});
_create
is the instance initialization method (we would call it a "constructor" in java), that is called automatically, when a
new instance object is created. Let's create a new instance of this class:
var myObj = new MyClass(42);
Now here is the trick: we can not access the private methods (_myPrivateMethod
, _create
), nor the member
fields (myField
) through myObj
. In fact, myObj
has only a single member: myPublicMethod
.
If we call myObj._myPrivateMethod()
or myObj._create()
, we will get an exception; if we read myObj._myField
,
we will always get undefined
.
Actually, the object returned by the new
operator (myObj
) is not the real instance object; myObj
is only a proxy object - I call it an "interface object" -, that contains proxy functions to the real public methods. The real instance
object is hidden behind the curtains: it is encapsulated. Anyway, we can use myObj
(the interface object) just as if
it was the real instance object (with the exception of the instanceof
operator; but we have a solution for that - keep
reading).
As you may have noticed, we use a naming convention here: if a method name starts with an underscore, it is considered private.
Fields are always considered private: we don’t have to start field names with an underscore.
Also, all of the own members
of the (real) instance object (in the sense of Object#hasOwnProperty
) are considered private.
Inheritance
We can define a subclass with the following syntax:
var MySubClass = oo.createClass(MyClass, {
_myPrivateMethod: function(bar) {
return this.myField + bar + 1;
}
});
There is an alternative syntax to define a subclass (a syntactic sugar):
var MySubClass = MyClass({
_myPrivateMethod: function(bar) {
return this.myField + bar + 1;
}
});
The above two subclass definitions are completely equivalent.
Subclasses can access the fields and the private methods of the superclass(es) (so we would call them "protected members" in java).
Invoking overridden methods
We can invoke overridden superclass methods with this._super(...)
. We have to supply the method name as the first parameter:
var MySubClass = MyClass({
_myPrivateMethod: function(bar) {
return this._super("_myPrivateMethod", bar) + 1;
}
});
There is also _superApply
; check the reference section.
The oo.Base class
oo.Base
is a class defined by the library, that we can use as a base class for our classes. It provides the following basic
features:
- Instance initialization support.
- Instance deinitialization support.
- instanceof support.
Instance initialization and deinitialization support
If we have an inheritance chain, and all the classes in the chain have a _create
method, only one of them is invoked
automatically when a new instance object is created (the _create
method of the subclass at the end of the chain). Of course,
we can invoke the overridden _create
methods with _super
- but we have to do it explicitly. That's where oo.Base
comes to help: It implements _create
in such a way, that it calls the _init
methods of all the classes in the
inheritance chain (if they exist) automatically.
oo.Base
also defines a public method called destroy. It implements destroy
in such a way that it calls the _dispose
methods of all the classes in the inheritance chain (if they exist) automatically.
var MyClass = oo.Base({
_init: function(bar) {
console.log("Initializing MyClass");
},
// define some other methods ...
_dispose: function() {
console.log("Deinitializing MyClass");
}
});
var MySubClass = MyClass({
_init: function(bar) {
console.log("Initializing MySubClass");
},
// define some other methods ...
_dispose: function() {
console.log("Deinitializing MySubClass");
}
});
var myObj = new MySubClass(); // will invoke MyClass#_init, MySubClass#_init, in this order
// do some work with myObj ...
myObj.destroy(); // will invoke MySubClass#_dispose, MyClass#_dispose, in this order
(The library itself does not implement any mechanisms to call the destroy
method automatically.)
There is also _addDestroyFn
; check the reference section.
instanceof support
Consider the following example:
var MyClass = oo.Base({
// define some class members ...
});
var myObj = new MyClass();
var b = myObj instanceof MyClass; // returns false
myObj instanceof MyClass
returns false
, because myObj
is not the real instance object, but
only the interface object. So how can we check, if myObj
is an interface object for MyClass
?
oo.Base
has an isInstanceOf
method; so we can do
b = myObj.isInstanceOf(MyClass); // returns true
The library defines a marker interface class oo.Interface
. All interface objects are instances of this class:
b = myObj instanceof oo.Interface; // returns true
And finally, the library provides a helper function oo.isInterfaceOf
. With this function we can check, if an arbitrary object
is an interface object for a class:
b = oo.isInterfaceOf(myObj, MyClass); // returns true
oo.isInterfaceOf
works according to the following algorithm:
- It checks if
myObj
is an instance of theoo.Interface
class. If not, it returnsfalse
. - It checks if
myObj
has a method namedisInstanceOf
. If not, it returnsundefined
. - It returns
myObj.isInstanceOf(MyClass)
.