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