Object-Oriented JavaScript
Table of Contents
Object-oriented programming principles and conventions can be added to the
practice of programming with JavaScript language. There is no doubt that the
object-oriented programming practice can give certain benefits in the entire
software lifecycle. That can be required when resolving challenges of
relatively complex development. For example, a GUI framework can be expressed
as a set of “classes” that fully define visual component hierarchy, i.e.
inheriting common functionality from the common parent, redefining methods,
etc. Developer's code becomes cleaner and better maintainable.
This article describes how you in JavaScript, which doesn't support
object-oriented features, make up for all it's limitations by using some simple
conventions, rules and tricks.
If you are familiar with standard features - skip the following chapter.
Custom javascript Object (we will call it
class for simplicity) can be
defined as a function:
function Component(name, data)
{
this._name = name;
// public property
this._data = data;
// public property
var someStaticData = null;
// private static variable
this.prototype.setName = _setName;
// public member function
this.prototype.getName = _getName;
// public member function
this.prototype.setData = _setData;
// public member function
this.prototype.getData = _getData;
// public member function
// function implementation
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 accesed from outside, they are private. So what they can 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 shouldnot
contain in it's implementation any class methods or properties.
This chapter explains how you can using limited abilities of Javascript make it
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;
// public member function
obj.prototype.getName = _getName;
// public member function
obj.prototype.setData = _setData;
// public member function
obj.prototype.getData = _getData;
// public member function
// function implementation
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 declaration and implementation from
Component class:
function Component(name, data)
{
this._name = name;// public property
this._data = data;// public property
}
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 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 lets put this line of code:
ComponentT(Component);
What happened? When function called it assignes 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 it's functionality in let's say
Control class:
function Control(parent)
{
this._parent = parent;// public property
}
Then we would call it like this:
ComponentT(Control);
Control class now inherits from
Component.If you want to be able
to inherit from particular class you should provide
Initializer. So we
do the same for
Control class but now we remove previous line of code
and put it inside
Initializer:
ControlT(Control);
function ControlT(obj)
{
ComponentT(obj);
obj.prototype.setParent = _setParent;
// public member function
obj.prototype.getParent = _getParent;
// public member function
obj.prototype.paint = _paint;
// public member function
function _setParent(parent)
{
this._parent = parent;
}
function _getParent()
{
return this._parent;
}
function paint()
{
// some code
}
}
Picture will be more clear (if you don't get it yet) when we create let's
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;
// public member function
obj.prototype.getText = _getText;
// public member function
obj.prototype.paint = _paint;
// public member function
function _setText(text)
{
this._text = text;
}
function _getText()
{
return this._text;
}
function paint()
{
// some code
}
}
That's not done yet, how about properties they 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
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;
// public member function
obj.prototype.setName = _setName;
// public member function
obj.prototype.getName = _getName;
// public member function
obj.prototype.setData = _setData;
// public member function
obj.prototype.getData = _getData;
// public member function
// function implementation
function _initialze(name)
{
this.setName(name);
}
// rest of implementation here
}
function ControlT(obj)
{
ComponentT(obj);
obj.prototype.initialze = _initialze;
// public member function
obj.prototype.setParent = _setParent;
// public member function
obj.prototype.getParent = _getParent;
// public member function
obj.prototype.paint = _paint;
// public member function
function _initialze(parent)
{
Component.prototype.initialze.call(this);
this.setParent(parent);
this.paint();
}
// rest of implementation here
}
function TextBoxT(obj)
{
ControlT(obj);
obj.prototype.initialze = _initialze;
// public member function
obj.prototype.setText = _setText;
// public member function
obj.prototype.getText = _getText;
// public member function
obj.prototype.paint = _paint;
// public member function
function _initialze(parent, text)
{
Control.prototype.initialze.call(this, parent);
this.setText(text);
}
// rest of implementation here
}
Now everything is inherited. Please notice this nifty
Control.prototype.initialze.call(this,
parent); in TextBoxT's initialze method, if you don't know, it calls
Control's implementation of initialze 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 base class method. As you
can see we come to the next OOP feature - POLYMORPHISM
From example above notice that each of the classes in hierarchy has it's own
initialze
method, method is
overwritten by subclass. So what happens if you have
an instance of
TextBox which extends
Control is created calling
initialze
method which in turn calls
Control's initialze 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 it's 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 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;// public member function
obj.prototype.getControl = _getControl;// public member function
}
function IBinder(obj)
{
obj.prototype.setControl = _setControl;
// public member function
obj.prototype.getControl = _getControl;
// public member function
function _setControl()
{
throw "Please implement setControl method.";
}
function _getControl()
{
throw "Please implement getControl method.";
}
}