Introduction
This article is a modified version of the article from March 31 (which was removed) with library attached compatible with both Internet Explorer and Firefox browsers. It describes how JavaScript which doesn't support object-oriented features, makes up for all its limitations by using some simple conventions, rules and tricks. In addition, we offer you to download some basic JavaScript controls including Menu
, TextBox
, Checkbox
, TableLayout
which are written using exactly these methods. To see an example and get more advanced controls like Grid
, ComboBox
, Calendar
, different input masks, please visit our site.
If you are familiar with the standard features - skip the following section.
Custom JavaScript Object (we will call it class for simplicity) can be defined as a function:
function Component(name, data)
{
this._name = name;
this._data = data;
var someStaticData = null;
this.prototype.setName = _setName;
this.prototype.getName = _getName;
this.prototype.setData = _setData;
this.prototype.getData = _getData;
function _setName(name)
{
this._name = name;
}
function _getName()
{
return this._name;
}
function _setData(data)
{
this._data = data;
}
function _getData()
{
return this._data;
}
function someStaticFunction()
{
}
}
As it can be seen from the example above, four public
methods are defined on the Component
class. The instance of the class can be created and methods can be called on the instance as follows:
var component = new Component();
component.setName("SomeName");
component.setData("some string data");
Also, the _name
and the _data
fields are the properties of the class. Unfortunately all properties of JavaScript classes can be accessed from outside - they are all public
:
component._name = "SomeName";
It is different with variables that are declared inside the class (see someStaticData
). They cannot be accessed from outside, they are private
. So what can they be used for? Thay are static
and these variables are shared between all instances of the class.
If method is not assigned to class's prototype (see above someStaticFunction
), it is like a variable described above private static
method - it should not contain in its implementation any class methods or properties.
This chapter explains how you can using limited abilities of JavaScript to make it an Object-oriented language. Also we would show some programming tricks that optimize performance.
Looking at earlier Component
class, how can we make it reusable or implement first OO feature - inheritance? What if we define some function which we call Initializer
that would take one parameter, class, and assign to it all required methods (we use suffix T for convenience):
function ComponentT(obj)
{
obj.prototype.setName = _setName;
obj.prototype.getName = _getName;
obj.prototype.setData = _setData;
obj.prototype.getData = _getData;
function _setName(name)
{
this._name = name;
}
function _getName()
{
return this._name;
}
function _setData(data)
{
this._data = data;
}
function _getData()
{
return this._data;
}
}
Please notice that instead of this, in prototype assignment we use argument obj
.
Then we remove all method declarations and implementation from the Component
class:
function Component(name, data)
{
this._name = name;
this._data = data;
}
With Initializer
also comes big performance boost - try to create 100 class instances (put 10 methods inside the class constructor), then use Initializer
for the same class, you will have the second approach work 10 times faster because prototypes are assigned only once instead of doing it on every object creation. Here we come to our first OOP feature - INHERITANCE.
Now let's put this line of code:
ComponentT(Component);
What happened? When function is called, it assigns all methods inside ComponentT
function to Component
- Component
inherits ComponentT
.
This is not useful if you have just one Component
, but if you need to extend its functionality in let's say Control
class:
function Control(parent)
{
this._parent = parent;
}
Then we would call it like this:
ComponentT(Control);
Control
class now inherits from Component
. If you want to be able to inherit from a particular class, you should provide Initializer
. So we do the same for Control
class but now we remove the previous line of code and put it inside Initializer
:
ControlT(Control);
function ControlT(obj)
{
ComponentT(obj);
obj.prototype.setParent = _setParent;
obj.prototype.getParent = _getParent;
obj.prototype.paint = _paint;
function _setParent(parent)
{
this._parent = parent;
}
function _getParent()
{
return this._parent;
}
function paint()
{
}
}
The picture will be more clear (if you don't get it yet) when we create TextBox
which would extend Control
which extends Component
:
function TextBox(parent, text)
{
this._text = text;
}
TextBoxT(TextBox);
function TextBoxT(obj)
{
ControlT(obj);
obj.prototype.setText = _setText;
obj.prototype.getText = _getText;
obj.prototype.paint = _paint;
function _setText(text)
{
this._text = text;
}
function _getText()
{
return this._text;
}
function paint()
{
}
}
That's not done yet, how about properties that are left uninherited.
This problem is solved by simple convention:
- We remove all properties from inside class functions (
Component
, Control
, Textbox
). - Put one member function in
Initializer
of every class and call it initialize
. - Inside implementation of this function, put corresponding properties.
- Call this function from inside each constructor function.
The complete resulting code will be:
function Component(name, data)
{
this.initialize(name, data);
}
function Control(parent)
{
this.initialize(parent);
}
function TextBox(parent, text)
{
this.initialize(parent, text);
}
function ComponentT(obj)
{
obj.prototype.initialze = _initialze;
obj.prototype.setName = _setName;
obj.prototype.getName = _getName;
obj.prototype.setData = _setData;
obj.prototype.getData = _getData;
function _initialze(name)
{
this.setName(name);
}
}
function ControlT(obj)
{
ComponentT(obj);
obj.prototype.initialze = _initialze;
obj.prototype.setParent = _setParent;
obj.prototype.getParent = _getParent;
obj.prototype.paint = _paint;
function _initialze(parent)
{
Component.prototype.initialze.call(this);
this.setParent(parent);
this.paint();
}
}
function TextBoxT(obj)
{
ControlT(obj);
obj.prototype.initialze = _initialze;
obj.prototype.setText = _setText;
obj.prototype.getText = _getText;
obj.prototype.paint = _paint;
function _initialze(parent, text)
{
Control.prototype.initialze.call(this, parent);
this.setText(text);
}
}
Now everything is inherited. Please notice this nifty Control.prototype.initialze.call(this, parent);
in TextBoxT
's initialize
method. If you don't know, it calls Control
's implementation of initialize
method buy passing itself as a reference telling function that this inside that function would be actually passed TextBox
instance. It also passes parent
as a first parameter. This allows to invoke the base class method. As you can see, we come to the next OOP feature - POLYMORPHISM.
From the example above, notice that each of the classes in the hierarchy has its own initialize
method, method is overwritten by subclass. So what happens if you have an instance of TextBox
which extends Control
created calling initialize
method which in turn calls Control
's initialize
method which calls paint
method?
var txtName = new TextBox(parent, "John Smith");
Both classes have paint
method but correct method of TextBox
will be called - this is called POLYMORPHISM.
You can declare static
methods in JavaScript as follows:
- It is declared as normal member function.
- In its implementation, you cannot use classes non-
static
properties or functions. - It is called from outside using class constructor function,
Control.prototype.foo();
Interfaces are created as classes with the difference that they don't have properties and in their function implementation, you throw an error - this forces developer to implement the method if interface is the class's hierarchy:
function BinderT(obj)
{
IBinder(obj);
obj.prototype.setControl = _setControl;
obj.prototype.getControl = _getControl;
}
function IBinder(obj)
{
obj.prototype.setControl = _setControl;
obj.prototype.getControl = _getControl;
function _setControl()
{
throw "Please implement setControl method.";
}
function _getControl()
{
throw "Please implement getControl method.";
}
}
History
- 7th April, 2008: Initial post