Object-Oriented Javascript

Posted Wednesday 24th May, 2006

Update October 2010: I’m currently following up this article with some notes about development method, and then again with a more in-depth look at current practices for object-oriented JavaScript. Hopefully, this should serve to correct some of my errors in this older article. Once you’ve finished reading this article, please read the following:

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.

Included in: Development, JavaScript, Web

Categories:

  1. Accessibility
  2. Agile
  3. Ajax
  4. Apache
  5. API
  6. Architecture
  7. Books
  8. Browsers
  9. CMS
  10. CouchDB
  11. CSS
  12. Design
  13. Development
  14. Django
  15. Email
  16. Events
  17. Gaming
  18. Grammar
  19. Hardware
  20. HTML
  21. HTTP
  22. Humour
  23. Idea
  24. Information Architecture
  25. JavaScript
  26. jQuery
  27. Lean
  28. Life
  29. Linux
  30. Literature
  31. Mac OS X
  32. Management
  33. Meme
  34. Microformats
  35. Monday
  36. MySQL
  37. Networking
  38. News
  39. Personal
  40. Photoshop
  41. PHP
  42. Process
  43. Python
  44. Reference
  45. REST
  46. Science
  47. SEO
  48. Server
  49. Site
  50. Sitepimp
  51. Social
  52. Spelling
  53. Syndication
  54. Testing
  55. The Future
  56. Thoughts
  57. Tools
  58. Tutorial
  59. Tutorials
  60. Typography
  61. UI
  62. UNIX
  63. Virtualisation
  64. Web
  65. Web Standards
  66. Widgets
  67. Wii
  68. Writing
  69. Xbox
  70. XHTML