Make Object-Oriented Programming Easier With Only Six Lines of JavaScript

Introduction

JavaScript, as we're told, is not a class-based object-oriented language. There's little explicit support for the object-oriented notion of classes and inheritance. However, everything you can do with classes and inheritance in an explicitly class-based language like Java or C# can be done in JavaScript with constructor functions and delegation through the prototype chain. These relationships have to be set up programmatically at runtime using idiomatic JavaScript. Given JavaScript's highly dynamic nature this turns out to be pretty simple.

The subclass function

Simple nonetheless, I find it worthwhile to abstract the idiomatic JavaScript away inside a function. This has the added bonus of making the inheritance relationship more explicit in the code and it addresses a couple of JavaScript "warts" in the process. Most of the JavaScript code I write is simple enough that it doesn't need inheritance. Whenever I find I do need it, however, I always take the time to write a function like the subclass function below. It's pretty simple, clocking in at a mere 6 lines of non-trivial JavaScript.

function subclass(constructor, superConstructor)
{
	function surrogateConstructor()
	{
	}

	surrogateConstructor.prototype = superConstructor.prototype;

	var prototypeObject = new surrogateConstructor();
	prototypeObject.constructor = constructor;

	constructor.prototype = prototypeObject;
}

Using the subclass function

Suppose we have two JavaScript constructor functions: NamedItem and NamedNumberedItem, where we want to be able to use NamedNumberedItem objects anywhere we use NamedItem objects (but not vice-versa). Furthermore, we want NamedNumberedItem to inherit NamedItem functionality rather than reimplementing it. In idiomatic JavaScript we'd express this relationship something like:

NamedNumberedItem.prototype = new NamedItem("")

Using the subclass function above, the same relationship can be expressed as:

subclass(NamedNumberedItem, NamedItem);

Below is an example implementation of the NamedItem and NamedNumberedItem "classes" using the subclass function. The example then creates instances of the two types of objects and queries them to demonstrate that the subclass function really does what you think it does.

// ---------------------------------------- class NamedItem

function NamedItem(name)
{
	this.name = name;
}

NamedItem.prototype.getDescription = function ()
{
	return this.name;
}


// ---------------------------------------- class NamedNumberedItem

subclass(NamedNumberedItem, NamedItem);

function NamedNumberedItem(name, number)
{
	NamedItem.call(this, name);
	this.number = number;
}

NamedNumberedItem.prototype.getDescription = function ()
{
	return this.name + ":" + this.number;
}

var namedItem = new NamedItem("foo");
var namedNumberedItem = new NamedNumberedItem("foo", 1);

alert(namedItem.getDescription());
alert(namedItem instanceof Object);
alert(namedItem instanceof NamedItem);
alert(namedItem instanceof NamedNumberedItem);
alert(namedItem.constructor);
alert(namedNumberedItem.getDescription());
alert(namedNumberedItem instanceof Object);
alert(namedNumberedItem instanceof NamedItem);
alert(namedNumberedItem instanceof NamedNumberedItem);
alert(namedNumberedItem.constructor);

There should be no surprises. However, we should compare the behavior of the same code, only using idiomatic JavaScript to set up the inheritance relationship. I'll do that next.

// ---------------------------------------- class NamedItem

function NamedItem(name)
{
	this.name = name;
}

NamedItem.prototype.getDescription = function ()
{
	return this.name;
}


// ---------------------------------------- class NamedNumberedItem

NamedNumberedItem.prototype = new NamedItem("");

function NamedNumberedItem(name, number)
{
	NamedItem.call(this, name);
	this.number = number;
}

NamedNumberedItem.prototype.getDescription = function ()
{
	return this.name + ":" + this.number;
}

var namedItem = new NamedItem("foo");
var namedNumberedItem = new NamedNumberedItem("foo", 1);

alert(namedItem.getDescription());
alert(namedItem instanceof Object);
alert(namedItem instanceof NamedItem);
alert(namedItem instanceof NamedNumberedItem);
alert(namedItem.constructor);
alert(namedNumberedItem.getDescription());
alert(namedNumberedItem instanceof Object);
alert(namedNumberedItem instanceof NamedItem);
alert(namedNumberedItem instanceof NamedNumberedItem);
alert(namedNumberedItem.constructor);

As the example code demonstrates, the two subclassing methods behave essentially the same way, but they do differ on two key points: The handling of instance variables in the prototype and the handling of the constructor property. In both of these cases, I contend that the behavior of the subclass function makes more sense and is less likely to lead to confusion.

The Handling of Instance Properties Compared

The idiomatic JavaScript code creates a NamedItem object to serve as the prototype object for all objects created by the NamedNumberedItem constructor. This means that NamedNumberedItem objects inherit all the properties of the NamedItem prototype. Some of these properties are class-wide properties shared by all members of the NamedItem "class", but other properties (the name property) is specic to that one object that is serving as the prototype.

Of course the problem is that while these properties may be instance properties to the prototype object, they act like class properties to all of NamedNumberedItem objects that inherit them. This can be a problem when the inherited methods that work with those properties expect them to act like instance properties. Imagine what can happen if such an inherited property is a mutable value like an array. There's a big semantic difference between modifying an array local to one object as opposed to modifying one shared by many objects.

Normally we get around this problem by having the subclass constructor chain to the superclass constructor before it does anything else. This way the superclass constructor gets to create instance properties that really are instance properties. These instance properties completely hide the ones in the prototype. So those properties may be superfluous but they're also harmless.

The subclass function sidesteps this whole issue. Instead of calling the superclass constructor function to create the prototype it uses a "surrogate" constructor instead. To create a proper prototype chain, the surrogate constructor "borrows" the prototype from the superclass constructor. However, it defines no instance properties and equally importantly takes no arguments, regardless of how many the superclass constructor itself takes. This solves two problems -- there are no superfluous instance-properties-that-aren't-really-instance-properties in the prototype, and the superclass function is off the hook for knowing how many and what kind of arguments to pass to the superclass constructor.

One nod that JavaScript does makes towards class-based object-oriented programming is the instanceof operator. This operator evaluates true if the first argument, an object, has been constructed by the second argument, a constructor function. It will also return true if the constructor was used to construct the object's prototype, which is to say that there's something like a subclass relationship between the two constructors.

You might expect that the use of the surrogate constructor would confound the instanceof operator. However, the instanceof operator relies on the prototype chain to determine "class" membership, rather than on an object's constructor property. There's a good reason for this, as we'll see next.

The constructor Property

One place the two subclassing approaches disagree is the constructor property. In both examples, the NamedItem object's constructor properties each evaluate to NamedItem just as you would expect. In the example using the subclass function, the NamedNumberedItem object's constructor evaluates to NamedNumberedItem, again, just as you would expect. In the idiomatic JavaScript example, however, the NamedNumberedItem object's constructor property evaluates to NamedItem -- the superclass. This is surprising, but it's also consistent with the ECMAScript standard.

This is because the constructor property is not an instance property, but rather lives in the prototype. More precisely it lives in a prototype. The problem here is that it's missing from the NamedNumberedItem prototype, which instead inherits the constructor property defined in the NamedItem prototype -- the value of which is the NamedItem constructor. The problem, very simply, is that JavaScript automatically assigns constructor properties to the default prototypes that it generates for functions. If you replace the default prototype with one of your own -- as we did when we "subclassed" NamedItem to get NamedNumberedItem -- then you get a prototype without a constructor instance property. The prototype object instead inherits the constructor property from its own prototype object.

We could have corrected this after setting up the prototype, like so:

NamedNumberedItem.prototype = new NamedItem("");
NamedNumberedItem.prototype.constructor = NamedNumberedItem;

This is rarely done in practice. Since the constructor property is rarely used, maybe it doesn't hurt anything. However, when inspecting an arbitrary object in JavaScript the constructor property is the only really convenient way you have of telling what kind of object you have. This can make debugging unneccesarily complicated and confusing.