First of all it should be said that OOP with JavaScript is a real pain.
I have read documents on the net where JavaScript is held high for its
simple OOP design but that's nonsense. Yes, JavaScript knows objects.
And yes, it is possible to create custom objects in a very simple way.
But one of the main reasons for OOP is inheritance and this is something
which is possible but absolutely dirty in JavaScript. But ok, here you are,
so you may be willing to learn it. Or maybe you know it better and can
teach me a more clean approach. Feel free to contact me.
Simple classes
Using simple classes without inheritance is very simple. A class is just
a function. This function is the constructor for the class. This constructor
is called every time you create an instance of this class. Inside the
constructor you can use the this qualifier to access object
properties. You don't need to declare properties (And you can't do it either).
Just simply access them. Here is a simple example:
// Declare the Person class
function Person(firstName, lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
// Create an instance of the Person class
var person = new Person("Arthur", "Dent");
// Output the name of the person
document.writeln(person.firstName + " " + person.lastName);
Impressed? No? I can understand this. Chances are high that the above is not
new to you. What you seek is more OOP. So what about class methods? Well, here
they come.
Class methods
JavaScript has no support for real classes. Instead it uses something called
prototypes. A prototype is an object which can be used as a template for
creating new objects. So what you want to do if you want to use class methods
is extending the prototype of the class you already have defined by writing
the constructor-like function like the Person function in the last
example. So lets implement our first method in the Person class. This method
should return a string representation of the object or in other words the
concatenated first name and last name of the person. Here we go:
Person.prototype.toString = function()
{
return this.firstName + " " + this.lastName;
};
Impressed? No? I know, it's ugly. But as far as I know it's the best way to do it. So what's
happening if you create an instance of the Person class now? JavaScript creates
a new object and uses the prototype of Person as a base object. And this
prototype now knows an additional method: The toString() method.
By the way: If you want to use method arguments just define them inside
the brackets behind the function keyword in the above example. They work
like normal function arguments.
So now after we have a toString() method we can rewrite our output
like this:
// Create an instance of the Person class
var person = new Person("Arthur", "Dent");
// Output the name of the person
document.writeln(person.toString());
Fine. Now we have a class, a constructor, properties and a method. Is this
OOP enough for you? Do you miss something? Ah, inheritance. Be prepared for some real
nasty stuff...
Inheritance
Simple but broken inheritance
As I already told you, JavaScript don't know real classes. It only knows
prototypes. So we have to do inheritance with that prototype stuff. The
basic is to use an instance of the class you want to extend as the base
prototype and then extend this prototype.
Some docs on the net suggests an approach like this:
function BaseClass()
{
// Constructor for base class
}
function NewClass()
{
// Constructor for new class
}
// Derive NewClass from BaseClass
NewClass.prototype = new BaseClass();
// Create instance of NewClass
var obj = new NewClass();
This approach has some drawbacks. As you can see the constructor of the
base class is called when creating the extended class. That's not what
we want! Especially this does not work with our Person class because we
have constructor arguments there and the above approach gives us no
possibility to pass them to the constructor. Sure, we can pass arguments
to the constructor in the line which derives NewClass from BaseClass but
there we don't know what values should be passed to the constructor. So
this whole approach is not useable and I will describe a somewhat more
complicated but working approach in the next section.
In the following examples we will declare a second class called NickPerson.
This class should extend the Person class and add another property
to it: A nickname.
More complex but working inheritance
In the last section I told you that the classical inheritance has the
drawback that constructors are not working as expected. A workaround for
this problem is creating a temporary class with the same prototype as the
super class we want to extend. From this temporary class we derive our
sub class. This task needs some code lines so it's a good idea to implement
them as methods inside JavaScript's internal Object class, so they
are easily usable everywhere.
Object.prototype.inherit = function(superClass)
{
var tmpClass = function() {};
tmpClass.prototype = superClass.prototype;
this.prototype = new tmpClass();
};
The inherit method creates a temporary class with an empty
constructor (so it doesn't matter if it is called because of inheritance).
This new temporary class uses the same prototype as the specified super
class. Then the prototype of the current class is overwritten with an
instance of the temporary class. That's all.
Now we are able to extend the Person class in real OOP manner. Here is the
code for the NickPerson class:
// Constructor for NickPerson class
function NickPerson(firstName, lastName, nickName)
{
// Call parent constructor
Person.call(this, firstName, lastName);
// Initialize new property
this.nickName = nickName;
}
// Derive NickPerson from Person
NickPerson.inherit(Person);
// Overwritten toString method
NickPerson.prototype.toString = function()
{
return this.firstName + " " + this.lastName
+ " (" + this.nickName + ")";
};
As you can see we call the inherit method as a static method of the
previously created NickPerson class. Because all objects (even class objects)
inherits all methods of the Object class the NickPerson class knows the
inherit method. The advantage in using such a static method is that we
don't have to specify the derived class as a parameter. The inherit method
already knows the derived class because it is the class for which the method
was called (so it's accessible with this). But you can also implement inherit as
a simple function which takes the child class and the parent class as parameters instead if your prefer not
touching the built-in Object class.
Call parent methods
For calling parent methods or the parent constructor we can use the call method
which is defined by the built-in Function class. The example above already uses this feature to call the parent constructor. The first parameter of the call method must always be the current object instance (this) and the remaining parameters are the method parameters. Calling a parent method works nearly the same way as calling a parent constructor. The only difference is that you have to call the method on the prototype of the class and not directly on the class. Here is another example which shows how to call the toString method of the Person class from within the overwritten toString method of the NickPerson class:
// Overwritten toString method
NickPerson.prototype.toString = function()
{
return Person.prototype.toString.call(this) + " (" + this.nickName + ")";
};
Here are the complete scripts described in this article and a demo page which uses them:
Static properties and methods
By looking at all the unusual stuff which is needed to reach working
inheritance it is nice to see that static properties and methods are
quite easy and intuitive to use. A class (or better a function) is also
some kind of object in JavaScript. So you can put properties and even
functions into the class itself like this:
// Define a class
function MyClass()
{
// Initialize/Increase a static instance counter property
if (MyClass.counter === undefined)
{
MyClass.counter = 1;
}
else
{
MyClass.counter++;
}
}
MyClass.resetCounter = function()
{
MyClass.counter = 0;
};
var obj1 = new MyClass();
var obj2 = new MyClass();
document.writeln("Instances: " + MyClass.counter);
You can access the static properties inside static methods and instance
methods exactly the same. Just use the class name as qualifier. Inside
static methods you can also access them via the this qualifier because
this is the class itself.