Is JavaScript Object-Oriented?

Programming languages that are object-oriented are considered to have powerful features above and beyond other, merely mortal languages. Moreover, the style in which developers must think when working with object-oriented languages differs from that of more traditional procedural languages like Pascal and C.

There's some debate about just exactly what features are required to make a language object-oriented. From a practical point of view though, these four features are probably enough to draw a Yes vote from most people:

Just for completeness, we should really add a fifth item to our small list. However this one is more of a consequence of the other four rather than a fifth necessary feature of object-oriented programming.

We'll look at each of these points in a minute, but to cut to the chase JavaScript scores highly on all fronts:

Inheritance is a crucial concept for object-orientedness. Without inheritance, or so-called "robust support" for inheritance (meaning enough proper features built into the language), JavaScript could only be said to be object-based, not object-oriented. Object-based languages miss out on a convenient fit with some powerful software modeling techniques such as Unified Modeling Language (UML). Strongly typed languages such as C++, Java or ADA might take such a view of JavaScript. However, other interpreted languages such as Smalltalk and Perl would probably agree with JavaScript, so it comes down to picking sides. Pragmatically, you get some language assistance with JavaScript for inheritance, so that's probably enough to qualify.

But the key question is simple. Why would you want or need to start writing JavaScript scripts in an object-oriented fashion? JavaScript is probably at its best when it leaves the complex object handling stuff to the host software that the interpreter is embedded in. Your script can thus stay short and sweet and achieve powerful effects through the host objects exposed with no real need for an OO thought in your head.

Consider the point down the line however when your script starts to collect dozens of custom-made JavaScript objects. Relying on JavaScript's relaxed syntax at this stage to whip together a quick and dirty script is not going to work for you much longer and you will begin to need a more planned and designed approach. JavaScript centers around objects, therefore an object-oriented design is a natural and sane fit.

JavaScript Support for OO Concepts

As you walk down OO Programming Parkway, you'll realize a need to understand how to implement the tenets of this modus operandi in actual code, so in the next few pages we'll go through how each of the five important object-oriented concepts mentioned above fit into JavaScript.

Encapsulation

You'll remember that in JavaScript, pretty much everything is the property of an object (if it's not a method). Now take the following example.

function area() {
  return this.dimension * this.dimension;
}

function Square(size) {
  this.dimension = size;
  this.area = size * size;
}

var object1 = new Square(4);  // make one

var object2 = new Object;
    object2.dimension = 4;
    object2.area = 16;   // make another

It's pretty obvious that all the script does is to create two effectively identical objects. They both have the same properties and the same values associated with those properties. So what we can see from here is that ignoring how they got those properties, both objects group the two together under one umbrella which we can refer to – they encapsulate the properties.

In this example, object1 is probably created in a more "object-like" manner since we bother to define all the features of the object in one place (in the constructor function Square()) before we make the object up. Functionally there is little difference.

In other object-oriented languages, information hiding goes hand-in-hand with encapsulation. This means that not only are the candidate data and functions collected together in one object, but also restrictions are placed on how the items can be extracted or accessed afterwards. Typically it is the candidate data items that are heavily restricted. In JavaScript, once the above objects are created there's nothing stopping you from getting at the dimension property thus:

var total = object1.dimension + object2.dimension;
object2.dimension = total;

If JavaScript had heavy restrictions you might have to create special methods to access the dimension property (after adding other special magic to hide it in the first place):

var total = object1.getSize() + object2.getSize();
object2.putSize(total);

You can still do this (except for the special magic required to do the hiding), but there's not much point. The solution for JavaScript is to relax – it's not that important. Access the property directly – the simple nature of JavaScript is designed to encourage a non-rigid approach.

Aggregation / Containment

Aggregation and containment in object-oriented terms mean the ability to store objects entirely inside other objects. As you've seen, there is some simple syntax to do this. Here's an example:

var apple = new Object();
var pear  = new Object();
var peach = new Object();

var fruitbowl = new Object();
fruitbowl.item1 = apple;
fruitbowl.item2 = pear;
fruitbowl.item3 = peach;

The last object, fruitbowl, has several properties that are themselves objects. Aggregation/containment isn't much different to encapsulation in this respect; it's just that whole objects are collected together into another object rather than object parts collected together into a single object. As before with encapsulation, you could worry about hiding the contained objects, but it's much easier just to relax. However, in this case you can get too relaxed as this further example shows:

var pickle = new Object();
object3.flavor = "extra tart";
pickle_jar1.contents = pickle;
pickle_jar2.contents = pickle;

Lines 1 and 2 are fair enough encapsulation – an object with a single property is created. But in lines 3 and 4 both pickle jars (assumedly they're objects) are (also assumedly) trying to contain the pickle. This is not allowed as only one object can contain another. 100% of one pickle won't go into both of two pickle jars. Okay, you could put the pickle in one jar and that jar inside the other jar, but that's a different script. That script would actually be an example of association which we'll come to later.

The example illustrates that JavaScript supports aggregation/containment, but doesn't enforce it. Using a loosely-typed language lets you relax, but on the other hand it's up to you to make sure you don't confuse yourself. Lines 3 and 4 illustrate association, not encapsulation, aggregation or containment. When the conceptual idea separates from the things that the language lets you get away with, that's when C++ and Java programmers get excited and say that JavaScript only has "weak" (as opposed to "robust”) support for OO. But as a relaxed scripter, who cares – as long as you accept the onus is on you to be clear about your design.

In an object oriented language: the peach is part of the fruitbowl, the fruitbowl contains a peach, or has a peach (sometimes officially written HAS-A).

Aggregation and containment are probably the most interesting object-like features to the scriptwriter. The reason is that Web browsers that host JavaScript have a large containment hierarchy (objects containing objects containing objects).

Inheritance

Inheritance in object-oriented terms means that an object's nature or essence or features come in part from itself, and in part from another object. It inherits the other object's features. That other object might be in the same position – in part described by itself and in part from a third object. In that case, the first object is also in part from the third. Objects can't be mutually derived from each other. Since there is a one-way order, the collection of interrelated object features is called an object hierarchy. Notice that the discussion focuses on object features not object instances. Features are descriptive of an object. An instance represents an actual object.

A classic example of inheritance comes from geometry:

geo

Here is a generic polygon, a polygon that is a rectangle, and a polygon that is a rectangle that is also a square. It's easy to see that rectangles inherit features from polygons. Polygons have a number of straight-edged sides that completely enclose a space and so do rectangles. However rectangles have some properties of their own as well: they must also have exactly 4 sides; opposite pairs of sides must be the same length; and all angles between adjacent sides must be 90 degrees. Further, it's true that squares fulfill all the rectangle criteria, so they can be said inherit the rectangle's features. Squares also have features uniquely their own. Not only must opposite sides of a square match in length and all angles be 90 degrees, but all sides must be equal in length as well.

The square depicted doesn't partially exist as part of the polygon depicted – clearly they are separate objects. It's just that the features of the more general polygon contribute to the features of the square. Sometimes you look or sound like one of your parents, but you're still yourself.

A bit of inheritance language: you can say squares inherit from rectangles. Squares are also the derived thing and rectangles are the base thing. Alternatively, squares are the child thing and rectangles the parent thing. It's also very common and expressively powerful to say "a square is a rectangle". This is sometime written IS-A to emphasize the specialized nature of the relationship. You can also say that a square specializes a rectangle, or that a rectangle generalizes a square.

For completeness, consider triangles. Triangles are polygons, but they're not rectangles. So it's possible for one base object type to have two different derived object types. The different derived object types are unrelated to each other. Such collections of inheritance relationships can be drawn like this:

geo2

This is an inheritance hierarchy where the arrows point towards the parent. A popular notation for object modeling diagrams is UML, which you can read more about in Instant UML by Pierre-Alain Muller (Wrox Press, 1997. ISBN 1-861000-87-1).

JavaScript Prototype Chains

Inheritance can get pretty complex, so the way JavaScript handles it is deferred until all the other object oriented concepts are disposed of. Nevertheless, it should be easy to see that prototype chains are a good candidate for inheritance-type behavior in JavaScript, since an object appears to have all the properties of its prototype object. Alas, if only it were that simple. Organizing JavaScript inheritance is left to the next main section in this pages because there are a number of options to consider.

Polymorphism / Late-Binding

Polymorphism and late-binding are concepts designed to make computer objects a little like real-world objects, by ensuring that using common behavior doesn't require an in-depth knowledge of the object. An example from the real world is picking up and throwing things. Most small things, like pebbles, sticks, fruit, books, eggs and shoes can all be picked up and thrown, and you don't need to know too much about them in order to get them to sail through the air. You certainly don't need to know how many pips the fruit has, or whether the shoelaces on the shoes are nicely done up. You may have to modify your behavior a bit for some less common throwable objects, like houses, cats and loved ones but the principle is there – the details don't matter too much.

For strongly typed languages such as C++ and Java, acquiring this kind of "don't care" behavior is not trivial because the nature of all available objects is supposed to be strictly and completely known at all times. For those languages, the problem is solved with the technique of late-binding which, very roughly speaking, defers the "need to know everything" requirement until a given program is actually running. Such languages normally need to know everything at compile (preparation) time.

In the JavaScript case, the language is designed from scratch so that you don't have to care:

var thingo = new Object;
...     // do anything with thingo
thingo.gently_lob();

In JavaScript, everything is late-bound, because the language is interpreted – decisions about whether methods or properties exist are made at the last second. Secondly, JavaScript objects are loosely-typed, so the language never had a need to rigidly understand objects in the first place. In the example, it doesn't matter where thingo comes from, or what type it is, you can easily try to gently_lob() it, and the worst that will happen is that you'll get an error saying "no gently_lob() method". If a gently_lob() method exists, it'll be called without fuss, as you'd expect.

From this example you can see that JavaScript has very straightforward support from polymorphism.

Association

Association is not a core requirement for object-orientedness in a language, but it is used so much in object-oriented design that it's worth covering for completeness. Association is a powerful concept because it is the only way that objects can have access to each other without the "sole ownership" implications of containment as discussed above.

Because any JavaScript variable can track any object, association is implemented very naturally in the language. A simple example:

// Object separate to other concerns 
var car = new Mini(); 
    car.capacity = 4;
    car.passengers = 0;

// Second as-yet unrelated object
var person = new Traveller();
    
if ( car.passengers < car.capacity ) {
  car.passengers++;
  person.transport = car; // associated here
}

In this example, many Traveller objects might use the same Mini object as their preferred transport. Hopefully checks will always be done so that sharing the object makes sense. In this case that means that no more than four Traveller objects should share the Mini object. When a Traveller object is finished with the Mini object, then this code might be used:

delete person.transport;
 
or alternatively

person.transport = null;

When this happens, the Mini object won't cease to exist, as the car variable will always point to it. But one less person will associate the Mini object with themselves.

For association, common language is to say: the Traveller object uses a Mini object. This is sometimes more formally written: USES-A. Because JavaScript provides no mechanism to distinguish between containment and association, it's up to the scriptwriter to put his design hat on and stay clear in his own mind which use is intended.