Object-Oriented Javascript
Following on from my earlier post, “Object-Oriented Concepts,” it’s time we started to have a look at some examples of execution. I’m going to start with Javascript because I believe this to have widest appeal - PHP, as a server-side language, is probably of interest to fewer developers so I’ll cover it later.
So without further ado, here’s how to objectify your javascript…
Objects in Javascript
Although Javascript shouldn’t be classed as an object-oriented language, pretty much everything within it is object based; from DOM scripting (Document Object Model) through to specific built-in objects such as Image and Date. This basically means that we can adopt some OOP concepts but not all.
Javascript handles objects in a number of ways; a developer can define an object and then instantiate with the new
operator, a developer can declare an object on the fly using the object literal or a developer can extend an existing object (either built-in or user-defined) using its prototype.
In fact, even data-types that can be declared literally, such as arrays and strings, can also be declared as objects. This is mainly so that object methods can be applied to literal values when needed. Javascript does this by temporarily converting your literal into an object. A good example would be the String.length
method.
Sounding complicated? Don’t worry, it’s really very easy. Let’s start getting our hands dirty…
The Object Literal
In programmer-speak, a “literal” is any value declared literally. Good examples would be string literals, array literals and boolean literals - the literal is the part after the “=” assignment operator:
// String literal
var my_string = "2468 This is a string";
// Array literal
var my_array = ['element1', 'element2', 'elephant'];
// Boolean literal
var my_boolean = true;
As a developer, you probably use these methods day-in, day-out; it still amazes me, however, how many programmers I speak to that don’t know the correct terminology!
In Javascript, we can also declare objects as a literal:
function getArea(radius)
{
// return the radius of our circle using the
// PI attribute of the built-in Math object
return (radius * radius) * Math.PI;
}
var circle = {
radius : 9,
getArea : getArea(this.radius)
};
Here we’ve defined an object, circle
, with a radius of 9. If we wanted to obtain the area of our circle, we could use the following line of code:
var my_radius = circle.getArea();
Fantastic! We’ve got ourselves a circle; but what happens when we want to declare more circles, all with varying radii?
Imagine we want to create lots of circle
objects using our object as a base - if we’ve declared using the object literal, we’re not able to do that! To a certain extent, that’s the difference between objects and classes (see my earlier post); if we want our object to behave more like a class (which, unfortunately, aren’t really supported in Javascript even though we can mimic the behaviour), we need to use objects that utilise the new
operator.
Note: For more information on the object literal, I recommend reading Chris Heilmann’s “Show Love to The Object Literal,” and Dustin Diaz’s “JSON for the Masses.”
The new
Operator
The new
operator creates an instance of any built-in or user-defined object - basically allowing us to re-use a user-defined obect (much like the behaviour of a class). Here are a couple of examples using built-in objects:
// Create a date object
var obj_today = new Date();
// Preload an image using the Image object
var obj_supper = new Image();
obj_supper.src = 'thelastsupper.jpg';
Take note that Javascript’s built-in objects do not always use a proper class interface - ie. you can set attributes without using a method. In most OOP languages this is considered bad practice and you can often prevent it with the use of access modifiers (public, private, protected). Even though Javascript lets us get away with this method, when using its built-in objects, I personally tend to write a proper interface for my own objects.
Ok, so that’s how you declare using the built-in objects, but what about something user-defined?
When using the new
keyword to declare user-defined objects, the objects require a constructor. Object structure (including an object’s constructor) is handled somewhat differently to most languages in Javascript. Here’s the syntax for defining this type of object:
function circle(radius)
{
this.radius = radius;
this.getArea = getArea;
// Don't forget that a constructor should
// always retain a valid state - so it should
// always return true
return true;
}
function getArea()
{
// return the radius of our circle using the
// PI attribute of the built-in Math object
return (this.radius * this.radius) * Math.PI;
}
In the example above, you’ll notice that the syntax is almost completely identical to declaring a function. This function is the constructor of our object and attributes and methods are declared within it, using the this
keyword.
Notice the Math object doesn’t need to be instantiated before we can use it - this is simply because it’s special and is always available for use.
We can now instantiate our object using the new
operator as before:
var obj_pizza = new circle(9);
And we can work out the area of our circle using the following line of code:
var flt_area = obj_pizza.getArea();
Encapsulation
There is one problem with the above definitions of an object, however, and that’s the lack of encapsulation. If we were to include another piece of code that used a getArea()
function (for instance a triangle
object), our circle
object’s getArea()
method would be over-written. To better encapsulate our methods, we can define our objects either literally like so:
var circle = {
radius : 9,
getArea : function()
{
return (this.radius * this.radius) * Math.PI;
}
};
Or non-literally like so:
function circle(radius)
{
this.radius = radius;
this.getArea = function()
{
return (this.radius * this.radius) * Math.PI;
};
return true;
}
In both cases this limits the scope of each getArea()
method to each circle
object.
Inheritance and prototype
The prototype
property is a useful feature in Javascript - it basically allows us to add attributes or methods to an object. This is a form of inheritance.
As a quick example of prototype
in use, let’s add a function to work out circumference:
function circle(radius)
{
this.radius = radius;
this.getArea = function()
{
return (this.radius * this.radius) * Math.PI;
};
return true;
}
// Add our new method
circle.prototype.getCircumference = function()
{
return this.radius * Math.PI * 2;
}
// Instantiate our object
var my_pizza = new circle(9);
// Call our new method
var flt_pizza_circ = my_pizza.getCircumference();
“But wait, “ I hear you cry, “what if we want to inherit an entire object?”
Not a problem, prototype
can handle this like so:
// Declare two objects - we're going to want Lion to
// inherit from cat
function cat()
{
this.eyes = 2;
this.legs = 4;
this.diet = 'carnivore';
return true;
}
function lion()
{
this.mane = true;
this.origin = 'Africa';
return true;
}
// Now comes the inheritance
lion.prototype = new cat();
// We can now obtain lion.diet
var simba = new lion();
var simba_diet = simba.diet;
Ok, so now that we understand inheritance (you do understand, right?), let’s take a look composition.
Composition
Association
Association, in Javascript, is pretty straight forward. Here’s an example:
// Define an object
function brick()
{
return true;
}
// Define an object
function wall()
{
this.brick1 = new brick();
this.brick2 = new brick();
this.brick3 = new brick();
return true;
}
Just like the pseudo-code example in my earlier post, we can see that the object “wall” now contains three instances of the “brick” object. This is association - the wall object “has” three bricks.
Now let’s take a look at aggregation.
Aggregation
Aggregation, as a relationship, is reliant on the ability to pass object and function parameters by reference. This basically means that a parameter represents a pointer to the actual object passed and not just a copy. Thankfully, Javascript handles all parameters in this way - which makes our aggregation relationship nice and easy:
function person
{
return true;
}
function car(driver)
{
this.driver = driver;
return true;
}
var me = new person();
var myMotor = new car(me);
This relationship allows us to “use” the “me” object within our “myMotor” object. We could also use the “me” object in any other objects that require it - and we’d only need that single instantiation. This is immensely helpful when we’re using objects that control behaviours such as XmlHttpRequest.
Update: Thanks to Jonathan Snook, I’ve recently discovered that Javascript doesn’t pass all parameters by reference - in fact working out how Javascript has handled your parameter can be quite confusing. In our particular case, the parameter is passed by reference because it is an object. When we’re not working with objects, this might not be the case. For more detailed information, please take a look at Jonathan’s excellent article, “Javascript: Passing by Value or by Reference.”
Update 2: Stefan Van Reeth’s fantastic comment, below, looks at references in a little more detail. I definitely recommend reading it as his explanation and examples may clear up confusion.
Polymorphism
Due to its object-based nature, Javascript handles polymorphism very well. Take a look at the following example:
function Person() {
//...
this.Speak = function() {
// ...
}
}
function Employee() {
// ...
this.Speak = function() {
// ...
}
}
AND (!!!)
Employee.prototype = new Person(); // inheritance
user emp = new Employee();
emp.Speak();
Employee inherits from Person and, as a result, already has a Speak() method. Because we want the Speak() method in Employee to do something different, we overload it with a new method definition. This is polymorphism in action.
Update: My original example for Polymorphism was entirely incorrect. Thanks go to “Joe is all you need to know” for pointing out my error and supplying this correct example.
Summary
So that’s a brief guide to adopting OOP practices in Javascript; I think you’ll agree that it adds real power when dealing with complicated scripts. However, that raises a common issue with OOP - you shouldn’t just use it for the sake of it. In fact, I often find that developers who are using these practices for everything are often the same developers who don’t understand the concepts fully.
My next OOP post will be regarding object-oriented PHP but, after an inquisitive email from Nate Logan, I also intend writing a piece about abstraction - the practice of reducing a process down to it’s root concepts and modelling them in the programming language of your choice.